Merge "Add locking to CredentialManagerServiceImpl Test: Built & deployed locally Bug: 253155340"
diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
index 1898e49..1147e07 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
@@ -622,7 +622,7 @@
public static final long DEFAULT_RUNTIME_MIN_USER_INITIATED_DATA_TRANSFER_GUARANTEE_MS =
Math.max(10 * MINUTE_IN_MILLIS, DEFAULT_RUNTIME_MIN_USER_INITIATED_GUARANTEE_MS);
public static final long DEFAULT_RUNTIME_USER_INITIATED_DATA_TRANSFER_LIMIT_MS =
- Math.max(Long.MAX_VALUE, DEFAULT_RUNTIME_USER_INITIATED_LIMIT_MS);
+ Math.min(Long.MAX_VALUE, DEFAULT_RUNTIME_USER_INITIATED_LIMIT_MS);
static final boolean DEFAULT_PERSIST_IN_SPLIT_FILES = true;
private static final boolean DEFAULT_USE_TARE_POLICY = false;
@@ -3174,10 +3174,9 @@
/** Returns the minimum amount of time we should let this job run before timing out. */
public long getMinJobExecutionGuaranteeMs(JobStatus job) {
synchronized (mLock) {
- final boolean shouldTreatAsDataTransfer = job.getJob().isDataTransfer()
- && checkRunLongJobsPermission(job.getSourceUid(), job.getSourcePackageName());
- if (job.shouldTreatAsUserInitiatedJob()) {
- if (shouldTreatAsDataTransfer) {
+ if (job.shouldTreatAsUserInitiatedJob()
+ && checkRunLongJobsPermission(job.getSourceUid(), job.getSourcePackageName())) {
+ if (job.getJob().isDataTransfer()) {
final long estimatedTransferTimeMs =
mConnectivityController.getEstimatedTransferTimeMs(job);
if (estimatedTransferTimeMs == ConnectivityController.UNKNOWN_TIME) {
@@ -3194,7 +3193,7 @@
));
}
return mConstants.RUNTIME_MIN_USER_INITIATED_GUARANTEE_MS;
- } else if (shouldTreatAsDataTransfer) {
+ } else if (job.getJob().isDataTransfer()) {
// For now, don't increase a bg data transfer's minimum guarantee.
return mConstants.RUNTIME_MIN_DATA_TRANSFER_GUARANTEE_MS;
} else if (job.shouldTreatAsExpeditedJob()) {
@@ -3213,23 +3212,18 @@
/** Returns the maximum amount of time this job could run for. */
public long getMaxJobExecutionTimeMs(JobStatus job) {
synchronized (mLock) {
- final boolean allowLongerJob;
- final boolean isDataTransfer = job.getJob().isDataTransfer();
- if (isDataTransfer || job.shouldTreatAsUserInitiatedJob()) {
- allowLongerJob =
- checkRunLongJobsPermission(job.getSourceUid(), job.getSourcePackageName());
- } else {
- allowLongerJob = false;
+ final boolean allowLongerJob = job.shouldTreatAsUserInitiatedJob()
+ && checkRunLongJobsPermission(job.getSourceUid(), job.getSourcePackageName());
+ if (job.getJob().isDataTransfer() && allowLongerJob) { // UI+DT
+ return mConstants.RUNTIME_USER_INITIATED_DATA_TRANSFER_LIMIT_MS;
+ }
+ if (allowLongerJob) { // UI with LRJ permission
+ return mConstants.RUNTIME_USER_INITIATED_LIMIT_MS;
}
if (job.shouldTreatAsUserInitiatedJob()) {
- if (isDataTransfer && allowLongerJob) {
- return mConstants.RUNTIME_USER_INITIATED_DATA_TRANSFER_LIMIT_MS;
- }
- if (allowLongerJob) {
- return mConstants.RUNTIME_USER_INITIATED_LIMIT_MS;
- }
return mConstants.RUNTIME_FREE_QUOTA_MAX_LIMIT_MS;
- } else if (isDataTransfer && allowLongerJob) {
+ }
+ if (job.getJob().isDataTransfer()) {
return mConstants.RUNTIME_DATA_TRANSFER_LIMIT_MS;
}
return Math.min(mConstants.RUNTIME_FREE_QUOTA_MAX_LIMIT_MS,
diff --git a/core/api/current.txt b/core/api/current.txt
index 9ef88c4..9da5b7e 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -1174,6 +1174,8 @@
field public static final int persistentDrawingCache = 16842990; // 0x10100ee
field public static final int persistentWhenFeatureAvailable = 16844131; // 0x1010563
field @Deprecated public static final int phoneNumber = 16843111; // 0x1010167
+ field public static final int physicalKeyboardHintLanguageTag;
+ field public static final int physicalKeyboardHintLayoutType;
field public static final int pivotX = 16843189; // 0x10101b5
field public static final int pivotY = 16843190; // 0x10101b6
field public static final int pointerIcon = 16844041; // 0x1010509
@@ -3092,6 +3094,7 @@
public abstract class AccessibilityService extends android.app.Service {
ctor public AccessibilityService();
method public void attachAccessibilityOverlayToDisplay(int, @NonNull android.view.SurfaceControl);
+ method public void attachAccessibilityOverlayToWindow(int, @NonNull android.view.SurfaceControl);
method public boolean clearCache();
method public boolean clearCachedSubtree(@NonNull android.view.accessibility.AccessibilityNodeInfo);
method public final void disableSelf();
@@ -4295,7 +4298,7 @@
method @Deprecated public final void removeDialog(int);
method public void reportFullyDrawn();
method public android.view.DragAndDropPermissions requestDragAndDropPermissions(android.view.DragEvent);
- method public void requestFullscreenMode(@NonNull int, @Nullable android.os.OutcomeReceiver<java.lang.Void,java.lang.Throwable>);
+ method public void requestFullscreenMode(int, @Nullable android.os.OutcomeReceiver<java.lang.Void,java.lang.Throwable>);
method public final void requestPermissions(@NonNull String[], int);
method public final void requestShowKeyboardShortcuts();
method @Deprecated public boolean requestVisibleBehind(boolean);
@@ -5871,10 +5874,15 @@
method @Deprecated public android.view.Window startActivity(String, android.content.Intent);
}
- public class LocaleConfig {
+ public class LocaleConfig implements android.os.Parcelable {
ctor public LocaleConfig(@NonNull android.content.Context);
+ ctor public LocaleConfig(@NonNull android.os.LocaleList);
+ method public int describeContents();
+ method @NonNull public static android.app.LocaleConfig fromResources(@NonNull android.content.Context);
method public int getStatus();
method @Nullable public android.os.LocaleList getSupportedLocales();
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.app.LocaleConfig> CREATOR;
field public static final int STATUS_NOT_SPECIFIED = 1; // 0x1
field public static final int STATUS_PARSING_FAILED = 2; // 0x2
field public static final int STATUS_SUCCESS = 0; // 0x0
@@ -5885,8 +5893,10 @@
public class LocaleManager {
method @NonNull public android.os.LocaleList getApplicationLocales();
method @NonNull @RequiresPermission(value="android.permission.READ_APP_SPECIFIC_LOCALES", conditional=true) public android.os.LocaleList getApplicationLocales(@NonNull String);
+ method @Nullable public android.app.LocaleConfig getOverrideLocaleConfig();
method @NonNull public android.os.LocaleList getSystemLocales();
method public void setApplicationLocales(@NonNull android.os.LocaleList);
+ method public void setOverrideLocaleConfig(@Nullable android.app.LocaleConfig);
}
public class MediaRouteActionProvider extends android.view.ActionProvider {
@@ -15046,6 +15056,7 @@
field public static final int FLEX_RGB_888 = 41; // 0x29
field public static final int HEIC = 1212500294; // 0x48454946
field public static final int JPEG = 256; // 0x100
+ field public static final int JPEG_R = 4101; // 0x1005
field public static final int NV16 = 16; // 0x10
field public static final int NV21 = 17; // 0x11
field public static final int PRIVATE = 34; // 0x22
@@ -17227,6 +17238,7 @@
field public static final int DATASPACE_DYNAMIC_DEPTH = 4098; // 0x1002
field public static final int DATASPACE_HEIF = 4100; // 0x1004
field public static final int DATASPACE_JFIF = 146931712; // 0x8c20000
+ field public static final int DATASPACE_JPEG_R = 4101; // 0x1005
field public static final int DATASPACE_SCRGB = 411107328; // 0x18810000
field public static final int DATASPACE_SCRGB_LINEAR = 406913024; // 0x18410000
field public static final int DATASPACE_SRGB = 142671872; // 0x8810000
@@ -19685,6 +19697,7 @@
public final class GnssCapabilities implements android.os.Parcelable {
method public int describeContents();
method @NonNull public java.util.List<android.location.GnssSignalType> getGnssSignalTypes();
+ method public boolean hasAccumulatedDeltaRange();
method public boolean hasAntennaInfo();
method public boolean hasGeofencing();
method @Deprecated public boolean hasGnssAntennaInfo();
@@ -19719,6 +19732,7 @@
ctor public GnssCapabilities.Builder(@NonNull android.location.GnssCapabilities);
method @NonNull public android.location.GnssCapabilities build();
method @NonNull public android.location.GnssCapabilities.Builder setGnssSignalTypes(@NonNull java.util.List<android.location.GnssSignalType>);
+ method @NonNull public android.location.GnssCapabilities.Builder setHasAccumulatedDeltaRange(boolean);
method @NonNull public android.location.GnssCapabilities.Builder setHasAntennaInfo(boolean);
method @NonNull public android.location.GnssCapabilities.Builder setHasGeofencing(boolean);
method @NonNull public android.location.GnssCapabilities.Builder setHasLowPowerMode(boolean);
@@ -21195,6 +21209,7 @@
method @NonNull public android.media.AudioTrack.Builder setAudioAttributes(@NonNull android.media.AudioAttributes) throws java.lang.IllegalArgumentException;
method @NonNull public android.media.AudioTrack.Builder setAudioFormat(@NonNull android.media.AudioFormat) throws java.lang.IllegalArgumentException;
method @NonNull public android.media.AudioTrack.Builder setBufferSizeInBytes(@IntRange(from=0) int) throws java.lang.IllegalArgumentException;
+ method @NonNull public android.media.AudioTrack.Builder setContext(@NonNull android.content.Context);
method @NonNull public android.media.AudioTrack.Builder setEncapsulationMode(int);
method @NonNull public android.media.AudioTrack.Builder setOffloadedPlayback(boolean);
method @NonNull public android.media.AudioTrack.Builder setPerformanceMode(int);
@@ -39598,6 +39613,15 @@
field @NonNull public static final android.os.Parcelable.Creator<android.service.credentials.CallingAppInfo> CREATOR;
}
+ public final class ClearCredentialStateRequest implements android.os.Parcelable {
+ ctor public ClearCredentialStateRequest(@NonNull android.service.credentials.CallingAppInfo, @NonNull android.os.Bundle);
+ method public int describeContents();
+ method @NonNull public android.service.credentials.CallingAppInfo getCallingAppInfo();
+ method @NonNull public android.os.Bundle getData();
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.service.credentials.ClearCredentialStateRequest> CREATOR;
+ }
+
public final class CreateCredentialRequest implements android.os.Parcelable {
ctor public CreateCredentialRequest(@NonNull android.service.credentials.CallingAppInfo, @NonNull String, @NonNull android.os.Bundle);
method public int describeContents();
@@ -39638,6 +39662,7 @@
method public abstract void onBeginCreateCredential(@NonNull android.service.credentials.BeginCreateCredentialRequest, @NonNull android.os.CancellationSignal, @NonNull android.os.OutcomeReceiver<android.service.credentials.BeginCreateCredentialResponse,android.credentials.CreateCredentialException>);
method public abstract void onBeginGetCredential(@NonNull android.service.credentials.BeginGetCredentialRequest, @NonNull android.os.CancellationSignal, @NonNull android.os.OutcomeReceiver<android.service.credentials.BeginGetCredentialResponse,android.credentials.GetCredentialException>);
method @NonNull public final android.os.IBinder onBind(@NonNull android.content.Intent);
+ method public abstract void onClearCredentialState(@NonNull android.service.credentials.ClearCredentialStateRequest, @NonNull android.os.CancellationSignal, @NonNull android.os.OutcomeReceiver<java.lang.Void,android.credentials.ClearCredentialStateException>);
field public static final String CAPABILITY_META_DATA_KEY = "android.credentials.capabilities";
field public static final String EXTRA_CREATE_CREDENTIAL_EXCEPTION = "android.service.credentials.extra.CREATE_CREDENTIAL_EXCEPTION";
field public static final String EXTRA_CREATE_CREDENTIAL_REQUEST = "android.service.credentials.extra.CREATE_CREDENTIAL_REQUEST";
@@ -40462,6 +40487,7 @@
public abstract class RecognitionService extends android.app.Service {
ctor public RecognitionService();
+ method public int getMaxConcurrentSessionsCount();
method public final android.os.IBinder onBind(android.content.Intent);
method protected abstract void onCancel(android.speech.RecognitionService.Callback);
method public void onCheckRecognitionSupport(@NonNull android.content.Intent, @NonNull android.speech.RecognitionService.SupportCallback);
@@ -40996,6 +41022,36 @@
field public static final int ROUTE_WIRED_OR_EARPIECE = 5; // 0x5
}
+ public final class CallEndpoint implements android.os.Parcelable {
+ ctor public CallEndpoint(@NonNull CharSequence, int, @NonNull android.os.ParcelUuid);
+ method public int describeContents();
+ method @NonNull public CharSequence getEndpointName();
+ method public int getEndpointType();
+ method @NonNull public android.os.ParcelUuid getIdentifier();
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.telecom.CallEndpoint> CREATOR;
+ field public static final int TYPE_BLUETOOTH = 2; // 0x2
+ field public static final int TYPE_EARPIECE = 1; // 0x1
+ field public static final int TYPE_SPEAKER = 4; // 0x4
+ field public static final int TYPE_STREAMING = 5; // 0x5
+ field public static final int TYPE_UNKNOWN = -1; // 0xffffffff
+ field public static final int TYPE_WIRED_HEADSET = 3; // 0x3
+ }
+
+ public final class CallEndpointException extends java.lang.RuntimeException implements android.os.Parcelable {
+ ctor public CallEndpointException(@Nullable String);
+ ctor public CallEndpointException(@Nullable String, int);
+ ctor public CallEndpointException(@Nullable String, int, @Nullable Throwable);
+ method public int describeContents();
+ method public int getCode();
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.telecom.CallEndpointException> CREATOR;
+ field public static final int ERROR_ANOTHER_REQUEST = 3; // 0x3
+ field public static final int ERROR_ENDPOINT_DOES_NOT_EXIST = 1; // 0x1
+ field public static final int ERROR_REQUEST_TIME_OUT = 2; // 0x2
+ field public static final int ERROR_UNSPECIFIED = 4; // 0x4
+ }
+
public abstract class CallRedirectionService extends android.app.Service {
ctor public CallRedirectionService();
method public final void cancelCall();
@@ -41045,13 +41101,14 @@
method public final boolean addConnection(android.telecom.Connection);
method @NonNull public static android.telecom.Conference createFailedConference(@NonNull android.telecom.DisconnectCause, @NonNull android.telecom.PhoneAccountHandle);
method public final void destroy();
- method public final android.telecom.CallAudioState getCallAudioState();
+ method @Deprecated public final android.telecom.CallAudioState getCallAudioState();
method public final java.util.List<android.telecom.Connection> getConferenceableConnections();
method public final int getConnectionCapabilities();
method public final int getConnectionProperties();
method public final long getConnectionStartElapsedRealtimeMillis();
method @IntRange(from=0) public final long getConnectionTime();
method public final java.util.List<android.telecom.Connection> getConnections();
+ method @NonNull public final android.telecom.CallEndpoint getCurrentCallEndpoint();
method public final android.telecom.DisconnectCause getDisconnectCause();
method public final android.os.Bundle getExtras();
method public final android.telecom.PhoneAccountHandle getPhoneAccountHandle();
@@ -41062,13 +41119,16 @@
method public final boolean isRingbackRequested();
method public void onAddConferenceParticipants(@NonNull java.util.List<android.net.Uri>);
method public void onAnswer(int);
- method public void onCallAudioStateChanged(android.telecom.CallAudioState);
+ method public void onAvailableCallEndpointsChanged(@NonNull java.util.List<android.telecom.CallEndpoint>);
+ method @Deprecated public void onCallAudioStateChanged(android.telecom.CallAudioState);
+ method public void onCallEndpointChanged(@NonNull android.telecom.CallEndpoint);
method public void onConnectionAdded(android.telecom.Connection);
method public void onDisconnect();
method public void onExtrasChanged(android.os.Bundle);
method public void onHold();
method public void onMerge(android.telecom.Connection);
method public void onMerge();
+ method public void onMuteStateChanged(boolean);
method public void onPlayDtmfTone(char);
method public void onReject();
method public void onSeparate(android.telecom.Connection);
@@ -41111,7 +41171,7 @@
method public final android.net.Uri getAddress();
method public final int getAddressPresentation();
method public final boolean getAudioModeIsVoip();
- method public final android.telecom.CallAudioState getCallAudioState();
+ method @Deprecated public final android.telecom.CallAudioState getCallAudioState();
method public final String getCallerDisplayName();
method public final int getCallerDisplayNamePresentation();
method public final int getCallerNumberVerificationStatus();
@@ -41119,6 +41179,7 @@
method public final java.util.List<android.telecom.Conferenceable> getConferenceables();
method public final int getConnectionCapabilities();
method public final int getConnectionProperties();
+ method @NonNull public final android.telecom.CallEndpoint getCurrentCallEndpoint();
method public final android.telecom.DisconnectCause getDisconnectCause();
method public final android.os.Bundle getExtras();
method public final int getState();
@@ -41132,13 +41193,16 @@
method public void onAddConferenceParticipants(@NonNull java.util.List<android.net.Uri>);
method public void onAnswer(int);
method public void onAnswer();
- method public void onCallAudioStateChanged(android.telecom.CallAudioState);
+ method public void onAvailableCallEndpointsChanged(@NonNull java.util.List<android.telecom.CallEndpoint>);
+ method @Deprecated public void onCallAudioStateChanged(android.telecom.CallAudioState);
+ method public void onCallEndpointChanged(@NonNull android.telecom.CallEndpoint);
method public void onCallEvent(String, android.os.Bundle);
method public void onDeflect(android.net.Uri);
method public void onDisconnect();
method public void onExtrasChanged(android.os.Bundle);
method public void onHandoverComplete();
method public void onHold();
+ method public void onMuteStateChanged(boolean);
method public void onPlayDtmfTone(char);
method public void onPostDialContinue(boolean);
method public void onPullExternalCall();
@@ -41159,7 +41223,8 @@
method public final void putExtras(@NonNull android.os.Bundle);
method public final void removeExtras(java.util.List<java.lang.String>);
method public final void removeExtras(java.lang.String...);
- method public void requestBluetoothAudio(@NonNull android.bluetooth.BluetoothDevice);
+ method @Deprecated public void requestBluetoothAudio(@NonNull android.bluetooth.BluetoothDevice);
+ method public final void requestCallEndpointChange(@NonNull android.telecom.CallEndpoint, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.lang.Void,android.telecom.CallEndpointException>);
method public void sendConnectionEvent(String, android.os.Bundle);
method public final void sendRemoteRttRequest();
method public final void sendRttInitiationFailure(int);
@@ -41168,7 +41233,7 @@
method public final void setActive();
method public final void setAddress(android.net.Uri, int);
method public final void setAudioModeIsVoip(boolean);
- method public final void setAudioRoute(int);
+ method @Deprecated public final void setAudioRoute(int);
method public final void setCallerDisplayName(String, int);
method public final void setCallerNumberVerificationStatus(int);
method public final void setConferenceableConnections(java.util.List<android.telecom.Connection>);
@@ -41420,18 +41485,23 @@
public abstract class InCallService extends android.app.Service {
ctor public InCallService();
method public final boolean canAddCall();
- method public final android.telecom.CallAudioState getCallAudioState();
+ method @Deprecated public final android.telecom.CallAudioState getCallAudioState();
method public final java.util.List<android.telecom.Call> getCalls();
+ method @NonNull public final android.telecom.CallEndpoint getCurrentCallEndpoint();
+ method public void onAvailableCallEndpointsChanged(@NonNull java.util.List<android.telecom.CallEndpoint>);
method public android.os.IBinder onBind(android.content.Intent);
method public void onBringToForeground(boolean);
method public void onCallAdded(android.telecom.Call);
- method public void onCallAudioStateChanged(android.telecom.CallAudioState);
+ method @Deprecated public void onCallAudioStateChanged(android.telecom.CallAudioState);
+ method public void onCallEndpointChanged(@NonNull android.telecom.CallEndpoint);
method public void onCallRemoved(android.telecom.Call);
method public void onCanAddCallChanged(boolean);
method public void onConnectionEvent(android.telecom.Call, String, android.os.Bundle);
+ method public void onMuteStateChanged(boolean);
method public void onSilenceRinger();
- method public final void requestBluetoothAudio(@NonNull android.bluetooth.BluetoothDevice);
- method public final void setAudioRoute(int);
+ method @Deprecated public final void requestBluetoothAudio(@NonNull android.bluetooth.BluetoothDevice);
+ method public final void requestCallEndpointChange(@NonNull android.telecom.CallEndpoint, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.lang.Void,android.telecom.CallEndpointException>);
+ method @Deprecated public final void setAudioRoute(int);
method public final void setMuted(boolean);
field public static final String SERVICE_INTERFACE = "android.telecom.InCallService";
}
@@ -43815,6 +43885,7 @@
public final class SignalThresholdInfo implements android.os.Parcelable {
method public int describeContents();
+ method public int getHysteresisDb();
method public static int getMaximumNumberOfThresholdsAllowed();
method public static int getMinimumNumberOfThresholdsAllowed();
method public int getRadioAccessNetworkType();
@@ -43837,6 +43908,7 @@
public static final class SignalThresholdInfo.Builder {
ctor public SignalThresholdInfo.Builder();
method @NonNull public android.telephony.SignalThresholdInfo build();
+ method @NonNull public android.telephony.SignalThresholdInfo.Builder setHysteresisDb(@IntRange(from=0) int);
method @NonNull public android.telephony.SignalThresholdInfo.Builder setRadioAccessNetworkType(int);
method @NonNull public android.telephony.SignalThresholdInfo.Builder setSignalMeasurementType(int);
method @NonNull public android.telephony.SignalThresholdInfo.Builder setThresholds(@NonNull int[]);
@@ -44105,6 +44177,7 @@
method public int getActiveSubscriptionInfoCountMax();
method @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) public android.telephony.SubscriptionInfo getActiveSubscriptionInfoForSimSlotIndex(int);
method @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) public java.util.List<android.telephony.SubscriptionInfo> getActiveSubscriptionInfoList();
+ method @NonNull @RequiresPermission(anyOf={android.Manifest.permission.READ_PHONE_STATE, "carrier privileges"}) public java.util.List<android.telephony.SubscriptionInfo> getAllSubscriptionInfoList();
method @NonNull public java.util.List<android.telephony.SubscriptionInfo> getCompleteActiveSubscriptionInfoList();
method public static int getDefaultDataSubscriptionId();
method public static int getDefaultSmsSubscriptionId();
@@ -44116,7 +44189,8 @@
method @NonNull @RequiresPermission(anyOf={android.Manifest.permission.READ_PHONE_NUMBERS, "android.permission.READ_PRIVILEGED_PHONE_STATE", "carrier privileges"}) public String getPhoneNumber(int, int);
method @NonNull @RequiresPermission(anyOf={android.Manifest.permission.READ_PHONE_NUMBERS, "android.permission.READ_PRIVILEGED_PHONE_STATE", "carrier privileges"}) public String getPhoneNumber(int);
method public static int getSlotIndex(int);
- method @Nullable public int[] getSubscriptionIds(int);
+ method public static int getSubscriptionId(int);
+ method @Deprecated @Nullable public int[] getSubscriptionIds(int);
method @NonNull public java.util.List<android.telephony.SubscriptionPlan> getSubscriptionPlans(int);
method @NonNull @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) public java.util.List<android.telephony.SubscriptionInfo> getSubscriptionsInGroup(@NonNull android.os.ParcelUuid);
method @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) public boolean isActiveSubscriptionId(int);
@@ -54428,6 +54502,8 @@
method public String getMode();
method @NonNull public CharSequence getNameOverride();
method public int getNameResId();
+ method @Nullable public android.icu.util.ULocale getPhysicalKeyboardHintLanguageTag();
+ method @NonNull public String getPhysicalKeyboardHintLayoutType();
method public boolean isAsciiCapable();
method public boolean isAuxiliary();
method public boolean overridesImplicitlyEnabledSubtype();
@@ -54442,6 +54518,7 @@
method public android.view.inputmethod.InputMethodSubtype.InputMethodSubtypeBuilder setIsAuxiliary(boolean);
method public android.view.inputmethod.InputMethodSubtype.InputMethodSubtypeBuilder setLanguageTag(String);
method public android.view.inputmethod.InputMethodSubtype.InputMethodSubtypeBuilder setOverridesImplicitlyEnabledSubtype(boolean);
+ method @NonNull public android.view.inputmethod.InputMethodSubtype.InputMethodSubtypeBuilder setPhysicalKeyboardHint(@Nullable android.icu.util.ULocale, @NonNull String);
method public android.view.inputmethod.InputMethodSubtype.InputMethodSubtypeBuilder setSubtypeExtraValue(String);
method public android.view.inputmethod.InputMethodSubtype.InputMethodSubtypeBuilder setSubtypeIconResId(int);
method public android.view.inputmethod.InputMethodSubtype.InputMethodSubtypeBuilder setSubtypeId(int);
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index 4ae3176..9e2f54f 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -3043,6 +3043,7 @@
field public static final int LOCK_STATE_DEFAULT = 0; // 0x0
field public static final int NAVIGATION_POLICY_DEFAULT_ALLOWED = 0; // 0x0
field public static final int NAVIGATION_POLICY_DEFAULT_BLOCKED = 1; // 0x1
+ field public static final int POLICY_TYPE_AUDIO = 1; // 0x1
field public static final int POLICY_TYPE_SENSORS = 0; // 0x0
field public static final int RECENTS_POLICY_ALLOW_IN_HOST_DEVICE_RECENTS = 1; // 0x1
}
@@ -13192,10 +13193,18 @@
public final class DataSpecificRegistrationInfo implements android.os.Parcelable {
method public int describeContents();
+ method public int getLteAttachExtraInfo();
+ method public int getLteAttachResultType();
method @Deprecated @NonNull public android.telephony.LteVopsSupportInfo getLteVopsSupportInfo();
method @Nullable public android.telephony.VopsSupportInfo getVopsSupportInfo();
method public void writeToParcel(android.os.Parcel, int);
field @NonNull public static final android.os.Parcelable.Creator<android.telephony.DataSpecificRegistrationInfo> CREATOR;
+ field public static final int LTE_ATTACH_EXTRA_INFO_CSFB_NOT_PREFERRED = 1; // 0x1
+ field public static final int LTE_ATTACH_EXTRA_INFO_NONE = 0; // 0x0
+ field public static final int LTE_ATTACH_EXTRA_INFO_SMS_ONLY = 2; // 0x2
+ field public static final int LTE_ATTACH_TYPE_COMBINED = 2; // 0x2
+ field public static final int LTE_ATTACH_TYPE_EPS_ONLY = 1; // 0x1
+ field public static final int LTE_ATTACH_TYPE_UNKNOWN = 0; // 0x0
}
public final class DataThrottlingRequest implements android.os.Parcelable {
@@ -13291,6 +13300,7 @@
method public boolean isEmergencyEnabled();
method public void writeToParcel(android.os.Parcel, int);
field public static final int REGISTRATION_STATE_DENIED = 3; // 0x3
+ field public static final int REGISTRATION_STATE_EMERGENCY = 6; // 0x6
field public static final int REGISTRATION_STATE_HOME = 1; // 0x1
field public static final int REGISTRATION_STATE_NOT_REGISTERED_OR_SEARCHING = 0; // 0x0
field public static final int REGISTRATION_STATE_NOT_REGISTERED_SEARCHING = 2; // 0x2
@@ -16030,6 +16040,7 @@
public class ImsSmsImplBase {
ctor public ImsSmsImplBase();
+ ctor public ImsSmsImplBase(@NonNull java.util.concurrent.Executor);
method public void acknowledgeSms(int, @IntRange(from=0, to=65535) int, int);
method public void acknowledgeSms(int, @IntRange(from=0, to=65535) int, int, @NonNull byte[]);
method public void acknowledgeSmsReport(int, @IntRange(from=0, to=65535) int, int);
@@ -16247,6 +16258,7 @@
method @IntRange(from=0) public int getConfigVersion();
method @NonNull public java.util.List<android.text.FontConfig.FontFamily> getFontFamilies();
method public long getLastModifiedTimeMillis();
+ method @NonNull public java.util.List<android.text.FontConfig.NamedFamilyList> getNamedFamilyLists();
method public void writeToParcel(@NonNull android.os.Parcel, int);
field @NonNull public static final android.os.Parcelable.Creator<android.text.FontConfig> CREATOR;
}
@@ -16276,7 +16288,7 @@
method public int describeContents();
method @NonNull public java.util.List<android.text.FontConfig.Font> getFontList();
method @NonNull public android.os.LocaleList getLocaleList();
- method @Nullable public String getName();
+ method @Deprecated @Nullable public String getName();
method public int getVariant();
method public void writeToParcel(@NonNull android.os.Parcel, int);
field @NonNull public static final android.os.Parcelable.Creator<android.text.FontConfig.FontFamily> CREATOR;
@@ -16285,6 +16297,14 @@
field public static final int VARIANT_ELEGANT = 2; // 0x2
}
+ public static final class FontConfig.NamedFamilyList implements android.os.Parcelable {
+ method public int describeContents();
+ method @NonNull public java.util.List<android.text.FontConfig.FontFamily> getFamilies();
+ method @NonNull public String getName();
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.text.FontConfig.NamedFamilyList> CREATOR;
+ }
+
}
package android.util {
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index 64e27601..1cfd644 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -2744,6 +2744,7 @@
method @IntRange(from=0) public int getConfigVersion();
method @NonNull public java.util.List<android.text.FontConfig.FontFamily> getFontFamilies();
method public long getLastModifiedTimeMillis();
+ method @NonNull public java.util.List<android.text.FontConfig.NamedFamilyList> getNamedFamilyLists();
method public void writeToParcel(@NonNull android.os.Parcel, int);
field @NonNull public static final android.os.Parcelable.Creator<android.text.FontConfig> CREATOR;
}
@@ -2773,7 +2774,7 @@
method public int describeContents();
method @NonNull public java.util.List<android.text.FontConfig.Font> getFontList();
method @NonNull public android.os.LocaleList getLocaleList();
- method @Nullable public String getName();
+ method @Deprecated @Nullable public String getName();
method public int getVariant();
method public void writeToParcel(@NonNull android.os.Parcel, int);
field @NonNull public static final android.os.Parcelable.Creator<android.text.FontConfig.FontFamily> CREATOR;
@@ -2782,6 +2783,14 @@
field public static final int VARIANT_ELEGANT = 2; // 0x2
}
+ public static final class FontConfig.NamedFamilyList implements android.os.Parcelable {
+ method public int describeContents();
+ method @NonNull public java.util.List<android.text.FontConfig.FontFamily> getFamilies();
+ method @NonNull public String getName();
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.text.FontConfig.NamedFamilyList> CREATOR;
+ }
+
public static final class Selection.MemoryTextWatcher implements android.text.TextWatcher {
ctor public Selection.MemoryTextWatcher();
method public void afterTextChanged(android.text.Editable);
diff --git a/core/java/android/accessibilityservice/AccessibilityService.java b/core/java/android/accessibilityservice/AccessibilityService.java
index 968ed87..25483f2 100644
--- a/core/java/android/accessibilityservice/AccessibilityService.java
+++ b/core/java/android/accessibilityservice/AccessibilityService.java
@@ -3439,4 +3439,20 @@
throw new RuntimeException(re);
}
}
+
+ /**
+ * Attaches an accessibility overlay {@link android.view.SurfaceControl} to the specified
+ * window. This method should be used when you want the overlay to move and resize as the parent
+ * window moves and resizes. To remove this overlay and free the associated resources, use
+ * <code> new SurfaceControl.Transaction().reparent(sc, null).apply();</code>.
+ *
+ * @param accessibilityWindowId The window id, from {@link AccessibilityWindowInfo#getId()}.
+ * @param sc the SurfaceControl containing the overlay content
+ */
+ public void attachAccessibilityOverlayToWindow(
+ int accessibilityWindowId, @NonNull SurfaceControl sc) {
+ Preconditions.checkNotNull(sc, "SurfaceControl cannot be null");
+ AccessibilityInteractionClient.getInstance(this)
+ .attachAccessibilityOverlayToWindow(mConnectionId, accessibilityWindowId, sc);
+ }
}
diff --git a/core/java/android/accessibilityservice/IAccessibilityServiceConnection.aidl b/core/java/android/accessibilityservice/IAccessibilityServiceConnection.aidl
index da13e73..e99932d 100644
--- a/core/java/android/accessibilityservice/IAccessibilityServiceConnection.aidl
+++ b/core/java/android/accessibilityservice/IAccessibilityServiceConnection.aidl
@@ -24,6 +24,7 @@
import android.os.Bundle;
import android.os.RemoteCallback;
import android.view.MagnificationSpec;
+import android.view.SurfaceControl;
import android.view.MotionEvent;
import android.view.SurfaceControl;
import android.view.accessibility.AccessibilityNodeInfo;
@@ -157,4 +158,6 @@
List<AccessibilityServiceInfo> getInstalledAndEnabledServices();
void attachAccessibilityOverlayToDisplay(int displayId, in SurfaceControl sc);
-}
+
+ void attachAccessibilityOverlayToWindow(int accessibilityWindowId, in SurfaceControl sc);
+}
\ No newline at end of file
diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java
index b9eb443..d1772e37 100644
--- a/core/java/android/app/Activity.java
+++ b/core/java/android/app/Activity.java
@@ -995,8 +995,13 @@
})
public @interface FullscreenModeRequest {}
+ /** Request type of {@link #requestFullscreenMode(int, OutcomeReceiver)}, to request exiting the
+ * requested fullscreen mode and restore to the previous multi-window mode.
+ */
public static final int FULLSCREEN_MODE_REQUEST_EXIT = 0;
-
+ /** Request type of {@link #requestFullscreenMode(int, OutcomeReceiver)}, to request enter
+ * fullscreen mode from multi-window mode.
+ */
public static final int FULLSCREEN_MODE_REQUEST_ENTER = 1;
private boolean mShouldDockBigOverlays;
@@ -3015,8 +3020,9 @@
/**
* Request to put the a freeform activity into fullscreen. This will only be allowed if the
* activity is on a freeform display, such as a desktop device. The requester has to be the
- * top-most activity and the request should be a response to a user input. When getting
- * fullscreen and receiving corresponding {@link #onConfigurationChanged(Configuration)} and
+ * top-most activity of the focused display, and the request should be a response to a user
+ * input. When getting fullscreen and receiving corresponding
+ * {@link #onConfigurationChanged(Configuration)} and
* {@link #onMultiWindowModeChanged(boolean, Configuration)}, the activity should relayout
* itself and the system bars' visibilities can be controlled as usual fullscreen apps.
*
@@ -3034,9 +3040,11 @@
* @param approvalCallback Optional callback, use {@code null} when not necessary. When the
* request is approved or rejected, the callback will be triggered. This
* will happen before any configuration change. The callback will be
- * dispatched on the main thread.
+ * dispatched on the main thread. If the request is rejected, the
+ * Throwable provided will be an {@link IllegalStateException} with a
+ * detailed message can be retrieved by {@link Throwable#getMessage()}.
*/
- public void requestFullscreenMode(@NonNull @FullscreenModeRequest int request,
+ public void requestFullscreenMode(@FullscreenModeRequest int request,
@Nullable OutcomeReceiver<Void, Throwable> approvalCallback) {
FullscreenRequestHandler.requestFullscreenMode(
request, approvalCallback, mCurrentConfig, getActivityToken());
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index a4c9f8c..4bc051ec 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -6815,11 +6815,9 @@
// Wait for debugger after we have notified the system to finish attach application
if (data.debugMode != ApplicationThreadConstants.DEBUG_OFF) {
- // XXX should have option to change the port.
- Debug.changeDebugPort(8100);
if (data.debugMode == ApplicationThreadConstants.DEBUG_WAIT) {
Slog.w(TAG, "Application " + data.info.getPackageName()
- + " is waiting for the debugger on port 8100...");
+ + " is waiting for the debugger ...");
try {
mgr.showWaitingForDebugger(mAppThread, true);
@@ -6834,10 +6832,6 @@
} catch (RemoteException ex) {
throw ex.rethrowFromSystemServer();
}
-
- } else {
- Slog.w(TAG, "Application " + data.info.getPackageName()
- + " can be debugged on port 8100...");
}
}
diff --git a/core/java/android/app/ILocaleManager.aidl b/core/java/android/app/ILocaleManager.aidl
index c38b64f..e6d40f2 100644
--- a/core/java/android/app/ILocaleManager.aidl
+++ b/core/java/android/app/ILocaleManager.aidl
@@ -17,6 +17,7 @@
package android.app;
+import android.app.LocaleConfig;
import android.os.LocaleList;
/**
@@ -29,7 +30,6 @@
* @hide
*/
interface ILocaleManager {
-
/**
* Sets a specified app’s app-specific UI locales.
*/
@@ -45,4 +45,7 @@
*/
LocaleList getSystemLocales();
+ void setOverrideLocaleConfig(String packageName, int userId, in LocaleConfig localeConfig);
+
+ LocaleConfig getOverrideLocaleConfig(String packageName, int userId);
}
diff --git a/core/java/android/app/LocaleConfig.aidl b/core/java/android/app/LocaleConfig.aidl
new file mode 100644
index 0000000..37e0847
--- /dev/null
+++ b/core/java/android/app/LocaleConfig.aidl
@@ -0,0 +1,19 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app;
+
+parcelable LocaleConfig;
\ No newline at end of file
diff --git a/core/java/android/app/LocaleConfig.java b/core/java/android/app/LocaleConfig.java
index bbe3ce3..5d50d29 100644
--- a/core/java/android/app/LocaleConfig.java
+++ b/core/java/android/app/LocaleConfig.java
@@ -25,6 +25,8 @@
import android.content.res.TypedArray;
import android.content.res.XmlResourceParser;
import android.os.LocaleList;
+import android.os.Parcel;
+import android.os.Parcelable;
import android.util.AttributeSet;
import android.util.Slog;
import android.util.Xml;
@@ -37,23 +39,27 @@
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.HashSet;
+import java.util.Locale;
import java.util.Set;
/**
* The LocaleConfig of an application.
- * Defined in an XML resource file with an {@code <locale-config>} element and
- * referenced in the manifest via {@code android:localeConfig} on
- * {@code <application>}.
+ * There are two sources. One is from an XML resource file with an {@code <locale-config>} element
+ * and referenced in the manifest via {@code android:localeConfig} on {@code <application>}. The
+ * other is that the application dynamically provides an override version which is persisted in
+ * {@link LocaleManager#setOverrideLocaleConfig(LocaleConfig)}.
*
- * <p>For more information, see
+ * <p>For more information about the LocaleConfig from an XML resource file, see
* <a href="https://developer.android.com/about/versions/13/features/app-languages#use-localeconfig">
* the section on per-app language preferences</a>.
*
* @attr ref android.R.styleable#LocaleConfig_Locale_name
* @attr ref android.R.styleable#AndroidManifestApplication_localeConfig
+ *
+ * <p>For more information about the LocaleConfig overridden by the application, see
+ * TODO(b/261528306): add link to guide
*/
-public class LocaleConfig {
-
+public class LocaleConfig implements Parcelable {
private static final String TAG = "LocaleConfig";
public static final String TAG_LOCALE_CONFIG = "locale-config";
public static final String TAG_LOCALE = "locale";
@@ -83,13 +89,46 @@
public @interface Status{}
/**
- * Returns the LocaleConfig for the provided application context
+ * Returns an override LocaleConfig if it has been set via
+ * {@link LocaleManager#setOverrideLocaleConfig(LocaleConfig)}. Otherwise, returns the
+ * LocaleConfig from the application resources.
*
- * @param context the context of the application
+ * @param context the context of the application.
*
* @see Context#createPackageContext(String, int).
*/
public LocaleConfig(@NonNull Context context) {
+ this(context, true);
+ }
+
+ /**
+ * Returns a LocaleConfig from the application resources regardless of whether any LocaleConfig
+ * is overridden via {@link LocaleManager#setOverrideLocaleConfig(LocaleConfig)}.
+ *
+ * @param context the context of the application.
+ *
+ * @see Context#createPackageContext(String, int).
+ */
+ @NonNull
+ public static LocaleConfig fromResources(@NonNull Context context) {
+ return new LocaleConfig(context, false);
+ }
+
+ private LocaleConfig(@NonNull Context context, boolean allowOverride) {
+ if (allowOverride) {
+ LocaleManager localeManager = context.getSystemService(LocaleManager.class);
+ if (localeManager == null) {
+ Slog.w(TAG, "LocaleManager is null, cannot get the override LocaleConfig");
+ return;
+ }
+ LocaleConfig localeConfig = localeManager.getOverrideLocaleConfig();
+ if (localeConfig != null) {
+ Slog.d(TAG, "Has the override LocaleConfig");
+ mStatus = localeConfig.getStatus();
+ mLocales = localeConfig.getSupportedLocales();
+ return;
+ }
+ }
int resId = 0;
Resources res = context.getResources();
try {
@@ -109,6 +148,38 @@
}
/**
+ * Return the LocaleConfig with any sequence of locales combined into a {@link LocaleList}.
+ *
+ * <p><b>Note:</b> Applications seeking to create an override LocaleConfig via
+ * {@link LocaleManager#setOverrideLocaleConfig(LocaleConfig)} should use this constructor to
+ * first create the LocaleConfig they intend the system to see as the override.
+ *
+ * <p><b>Note:</b> The creation of this LocaleConfig does not automatically mean it will
+ * become the override config for an application. Any LocaleConfig desired to be the override
+ * must be passed into the {@link LocaleManager#setOverrideLocaleConfig(LocaleConfig)},
+ * otherwise it will not persist or affect the system’s understanding of app-supported
+ * resources.
+ *
+ * @param locales the desired locales for a specified application
+ */
+ public LocaleConfig(@NonNull LocaleList locales) {
+ mStatus = STATUS_SUCCESS;
+ mLocales = locales;
+ }
+
+ /**
+ * Instantiate a new LocaleConfig from the data in a Parcel that was
+ * previously written with {@link #writeToParcel(Parcel, int)}.
+ *
+ * @param in The Parcel containing the previously written LocaleConfig,
+ * positioned at the location in the buffer where it was written.
+ */
+ private LocaleConfig(@NonNull Parcel in) {
+ mStatus = in.readInt();
+ mLocales = in.readTypedObject(LocaleList.CREATOR);
+ }
+
+ /**
* Parse the XML content and get the locales supported by the application
*/
private void parseLocaleConfig(XmlResourceParser parser, Resources res)
@@ -165,4 +236,52 @@
public @Status int getStatus() {
return mStatus;
}
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ dest.writeInt(mStatus);
+ dest.writeTypedObject(mLocales, flags);
+ }
+
+ public static final @NonNull Parcelable.Creator<LocaleConfig> CREATOR =
+ new Parcelable.Creator<LocaleConfig>() {
+ @Override
+ public LocaleConfig createFromParcel(Parcel source) {
+ return new LocaleConfig(source);
+ }
+
+ @Override
+ public LocaleConfig[] newArray(int size) {
+ return new LocaleConfig[size];
+ }
+ };
+
+ /**
+ * Compare whether the locale is existed in the {@code mLocales} of the LocaleConfig.
+ *
+ * @param locale The {@link Locale} to compare for.
+ *
+ * @return true if the locale is existed in the {@code mLocales} of the LocaleConfig, false
+ * otherwise.
+ *
+ * @hide
+ */
+ public boolean containsLocale(Locale locale) {
+ if (mLocales == null) {
+ return false;
+ }
+
+ for (int i = 0; i < mLocales.size(); i++) {
+ if (LocaleList.matchesLanguageAndScript(mLocales.get(i), locale)) {
+ return true;
+ }
+ }
+
+ return false;
+ }
}
diff --git a/core/java/android/app/LocaleManager.java b/core/java/android/app/LocaleManager.java
index 70c014f..b62f766 100644
--- a/core/java/android/app/LocaleManager.java
+++ b/core/java/android/app/LocaleManager.java
@@ -18,6 +18,7 @@
import android.Manifest;
import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.annotation.RequiresPermission;
import android.annotation.SystemApi;
import android.annotation.SystemService;
@@ -29,8 +30,9 @@
import android.os.RemoteException;
/**
- * This class gives access to system locale services. These services allow applications to control
- * granular locale settings (such as per-app locales).
+ * This class gives access to system locale services. These services allow applications to
+ * control granular locale settings (such as per-app locales) or override their list of supported
+ * locales while running.
*
* <p> Third party applications should treat this as a write-side surface, and continue reading
* locales via their in-process {@link LocaleList}s.
@@ -106,8 +108,8 @@
private void setApplicationLocales(@NonNull String appPackageName, @NonNull LocaleList locales,
boolean fromDelegate) {
try {
- mService.setApplicationLocales(appPackageName, mContext.getUser().getIdentifier(),
- locales, fromDelegate);
+ mService.setApplicationLocales(appPackageName, mContext.getUserId(), locales,
+ fromDelegate);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -144,8 +146,7 @@
@NonNull
public LocaleList getApplicationLocales(@NonNull String appPackageName) {
try {
- return mService.getApplicationLocales(appPackageName, mContext.getUser()
- .getIdentifier());
+ return mService.getApplicationLocales(appPackageName, mContext.getUserId());
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -187,4 +188,49 @@
}
}
+ /**
+ * Sets the override LocaleConfig for the calling app.
+ *
+ * <p><b>Note:</b> Only the app itself with the same user can override its own LocaleConfig.
+ *
+ * <p><b>Note:</b> This function takes in a {@link LocaleConfig} which is intended to
+ * override the original config in the application’s resources. This LocaleConfig will become
+ * the override config, and stored in a system file for future access.
+ *
+ * <p><b>Note:</b> Using this function, applications can update their list of supported
+ * locales while running, without an update of the application’s software. For more
+ * information, see TODO(b/261528306): add link to guide.
+ *
+ * <p>Applications can remove the override LocaleConfig with a {@code null} object.
+ *
+ * @param localeConfig the desired {@link LocaleConfig} for the calling app.
+ */
+ @UserHandleAware
+ public void setOverrideLocaleConfig(@Nullable LocaleConfig localeConfig) {
+ try {
+ // The permission android.Manifest.permission#SET_APP_SPECIFIC_LOCALECONFIG is
+ // required to set an override LocaleConfig of another packages
+ mService.setOverrideLocaleConfig(mContext.getPackageName(), mContext.getUserId(),
+ localeConfig);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Returns the override LocaleConfig for the calling app.
+ *
+ * @return the override LocaleConfig, or {@code null} if the LocaleConfig isn't overridden.
+ */
+ @Nullable
+ @UserHandleAware
+ public LocaleConfig getOverrideLocaleConfig() {
+ try {
+ return mService.getOverrideLocaleConfig(mContext.getPackageName(),
+ mContext.getUserId());
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
}
diff --git a/core/java/android/companion/virtual/VirtualDeviceManager.java b/core/java/android/companion/virtual/VirtualDeviceManager.java
index cc452e2..f851c31 100644
--- a/core/java/android/companion/virtual/VirtualDeviceManager.java
+++ b/core/java/android/companion/virtual/VirtualDeviceManager.java
@@ -16,6 +16,8 @@
package android.companion.virtual;
+import static android.media.AudioManager.AUDIO_SESSION_ID_GENERATE;
+
import android.annotation.CallbackExecutor;
import android.annotation.IntDef;
import android.annotation.IntRange;
@@ -255,6 +257,42 @@
}
/**
+ * Returns device-specific audio session id for audio playback.
+ *
+ * @param deviceId - id of the virtual audio device
+ * @return Device specific session id to be used for audio playback (see
+ * {@link android.media.AudioManager.generateAudioSessionId}) if virtual device has
+ * {@link VirtualDeviceParams.POLICY_TYPE_AUDIO} set to
+ * {@link VirtualDeviceParams.DEVICE_POLICY_CUSTOM} and Virtual Audio Device
+ * is configured in context-aware mode.
+ * Otherwise {@link AUDIO_SESSION_ID_GENERATE} constant is returned.
+ * @hide
+ */
+ public int getAudioPlaybackSessionId(int deviceId) {
+ //TODO - Return session id rerouted to VirtualAudioDevice if the VirtualAudioDevice
+ //is configured to operate in context-aware mode.
+ return AUDIO_SESSION_ID_GENERATE;
+ }
+
+ /**
+ * Returns device-specific audio session id for audio recording.
+ *
+ * @param deviceId - id of the virtual audio device
+ * @return Device specific session id to be used for audio recording (see
+ * {@link android.media.AudioManager.generateAudioSessionId}) if virtual device has
+ * {@link VirtualDeviceParams.POLICY_TYPE_AUDIO} set to
+ * {@link VirtualDeviceParams.DEVICE_POLICY_CUSTOM} and Virtual Audio Device
+ * is configured in context-aware mode.
+ * Otherwise {@link AUDIO_SESSION_ID_GENERATE} constant is returned.
+ * @hide
+ */
+ public int getAudioRecordingSessionId(int deviceId) {
+ //TODO - Return session id corresponding to VirtualAudioDevice injection if the
+ // VirtualAudioDevice is configured to operate in context-aware mode.
+ return AUDIO_SESSION_ID_GENERATE;
+ }
+
+ /**
* A virtual device has its own virtual display, audio output, microphone, and camera etc. The
* creator of a virtual device can take the output from the virtual display and stream it over
* to another device, and inject input events that are received from the remote device.
diff --git a/core/java/android/companion/virtual/VirtualDeviceParams.java b/core/java/android/companion/virtual/VirtualDeviceParams.java
index be77f2b..f59a7a9 100644
--- a/core/java/android/companion/virtual/VirtualDeviceParams.java
+++ b/core/java/android/companion/virtual/VirtualDeviceParams.java
@@ -129,7 +129,7 @@
* a given policy type.
* @hide
*/
- @IntDef(prefix = "POLICY_TYPE_", value = {POLICY_TYPE_SENSORS})
+ @IntDef(prefix = "POLICY_TYPE_", value = {POLICY_TYPE_SENSORS, POLICY_TYPE_AUDIO})
@Retention(RetentionPolicy.SOURCE)
@Target({ElementType.TYPE_PARAMETER, ElementType.TYPE_USE})
public @interface PolicyType {}
@@ -147,6 +147,21 @@
*/
public static final int POLICY_TYPE_SENSORS = 0;
+ /**
+ * Tells the audio framework whether to configure the players ({@link android.media.AudioTrack},
+ * {@link android.media.MediaPlayer}, {@link android.media.SoundPool} and recorders
+ * {@link android.media.AudioRecord}) to use specific session ids re-routed to
+ * VirtualAudioDevice.
+ *
+ * <ul>
+ * <li>{@link #DEVICE_POLICY_DEFAULT}: fall back to default session id handling.
+ * <li>{@link #DEVICE_POLICY_CUSTOM}: audio framework will assign device specific session
+ * ids to players and recorders constructed within device context. The session ids are
+ * used to re-route corresponding audio streams to VirtualAudioDevice.
+ * <ul/>
+ */
+ public static final int POLICY_TYPE_AUDIO = 1;
+
/** @hide */
@IntDef(flag = true, prefix = "RECENTS_POLICY_",
value = {RECENTS_POLICY_ALLOW_IN_HOST_DEVICE_RECENTS})
diff --git a/core/java/android/content/IntentFilter.java b/core/java/android/content/IntentFilter.java
index bff27d3..611ad88 100644
--- a/core/java/android/content/IntentFilter.java
+++ b/core/java/android/content/IntentFilter.java
@@ -2890,6 +2890,29 @@
*/
}
+ /**
+ * Perform a check on data paths and scheme specific parts of the intent filter.
+ * Return true if it passed.
+ * @hide
+ */
+ public boolean checkDataPathAndSchemeSpecificParts() {
+ final int numDataPath = mDataPaths == null
+ ? 0 : mDataPaths.size();
+ final int numDataSchemeSpecificParts = mDataSchemeSpecificParts == null
+ ? 0 : mDataSchemeSpecificParts.size();
+ for (int i = 0; i < numDataPath; i++) {
+ if (!mDataPaths.get(i).check()) {
+ return false;
+ }
+ }
+ for (int i = 0; i < numDataSchemeSpecificParts; i++) {
+ if (!mDataSchemeSpecificParts.get(i).check()) {
+ return false;
+ }
+ }
+ return true;
+ }
+
/** @hide */
public IntentFilter(Parcel source) {
List<String> actions = new ArrayList<>();
diff --git a/core/java/android/content/pm/PackageInstaller.java b/core/java/android/content/pm/PackageInstaller.java
index 8deecd7..febdaed 100644
--- a/core/java/android/content/pm/PackageInstaller.java
+++ b/core/java/android/content/pm/PackageInstaller.java
@@ -907,6 +907,9 @@
/**
* Similar to {@link #checkInstallConstraints(List, InstallConstraints, Executor, Consumer)},
* but the callback is invoked only when the constraints are satisfied or after timeout.
+ * <p>
+ * Note: the device idle constraint might take a long time to evaluate. The system will
+ * ensure the constraint is evaluated completely before handling timeout.
*
* @param callback Called when the constraints are satisfied or after timeout.
* Intents sent to this callback contain:
diff --git a/core/java/android/hardware/DataSpace.java b/core/java/android/hardware/DataSpace.java
index 15eae09..0a14574 100644
--- a/core/java/android/hardware/DataSpace.java
+++ b/core/java/android/hardware/DataSpace.java
@@ -408,6 +408,19 @@
*/
public static final int DATASPACE_HEIF = 4100;
+ /**
+ * ISO/IEC TBD
+ *
+ * JPEG image with embedded recovery map following the Jpeg/R specification.
+ *
+ * <p>This value must always remain aligned with the public ImageFormat Jpeg/R definition and is
+ * valid with formats:
+ * HAL_PIXEL_FORMAT_BLOB: JPEG image encoded by Jpeg/R encoder according to ISO/IEC TBD.
+ * The image contains a standard SDR JPEG and a recovery map. Jpeg/R decoders can use the
+ * map to recover the input image.</p>
+ */
+ public static final int DATASPACE_JPEG_R = 4101;
+
/** @hide */
@Retention(RetentionPolicy.SOURCE)
@IntDef(flag = true, value = {
@@ -626,6 +639,7 @@
DATASPACE_DEPTH,
DATASPACE_DYNAMIC_DEPTH,
DATASPACE_HEIF,
+ DATASPACE_JPEG_R,
DATASPACE_UNKNOWN,
DATASPACE_SCRGB_LINEAR,
DATASPACE_SRGB,
diff --git a/core/java/android/hardware/camera2/CameraCharacteristics.java b/core/java/android/hardware/camera2/CameraCharacteristics.java
index 6e72b5f..a6f7e94 100644
--- a/core/java/android/hardware/camera2/CameraCharacteristics.java
+++ b/core/java/android/hardware/camera2/CameraCharacteristics.java
@@ -5562,6 +5562,115 @@
public static final Key<Integer> AUTOMOTIVE_LOCATION =
new Key<Integer>("android.automotive.location", int.class);
+ /**
+ * <p>The available Jpeg/R stream
+ * configurations that this camera device supports
+ * (i.e. format, width, height, output/input stream).</p>
+ * <p>The configurations are listed as <code>(format, width, height, input?)</code> tuples.</p>
+ * <p>If the camera device supports Jpeg/R, it will support the same stream combinations with
+ * Jpeg/R as it does with P010. The stream combinations with Jpeg/R (or P010) supported
+ * by the device is determined by the device's hardware level and capabilities.</p>
+ * <p>All the static, control, and dynamic metadata tags related to JPEG apply to Jpeg/R formats.
+ * Configuring JPEG and Jpeg/R streams at the same time is not supported.</p>
+ * <p><b>Optional</b> - The value for this key may be {@code null} on some devices.</p>
+ * <p><b>Limited capability</b> -
+ * Present on all camera devices that report being at least {@link CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL_LIMITED HARDWARE_LEVEL_LIMITED} devices in the
+ * {@link CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL android.info.supportedHardwareLevel} key</p>
+ *
+ * @see CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL
+ * @hide
+ */
+ public static final Key<android.hardware.camera2.params.StreamConfiguration[]> JPEGR_AVAILABLE_JPEG_R_STREAM_CONFIGURATIONS =
+ new Key<android.hardware.camera2.params.StreamConfiguration[]>("android.jpegr.availableJpegRStreamConfigurations", android.hardware.camera2.params.StreamConfiguration[].class);
+
+ /**
+ * <p>This lists the minimum frame duration for each
+ * format/size combination for Jpeg/R output formats.</p>
+ * <p>This should correspond to the frame duration when only that
+ * stream is active, with all processing (typically in android.*.mode)
+ * set to either OFF or FAST.</p>
+ * <p>When multiple streams are used in a request, the minimum frame
+ * duration will be max(individual stream min durations).</p>
+ * <p>See {@link CaptureRequest#SENSOR_FRAME_DURATION android.sensor.frameDuration} and
+ * android.scaler.availableStallDurations for more details about
+ * calculating the max frame rate.</p>
+ * <p><b>Units</b>: (format, width, height, ns) x n</p>
+ * <p><b>Optional</b> - The value for this key may be {@code null} on some devices.</p>
+ * <p><b>Limited capability</b> -
+ * Present on all camera devices that report being at least {@link CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL_LIMITED HARDWARE_LEVEL_LIMITED} devices in the
+ * {@link CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL android.info.supportedHardwareLevel} key</p>
+ *
+ * @see CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL
+ * @see CaptureRequest#SENSOR_FRAME_DURATION
+ * @hide
+ */
+ public static final Key<android.hardware.camera2.params.StreamConfigurationDuration[]> JPEGR_AVAILABLE_JPEG_R_MIN_FRAME_DURATIONS =
+ new Key<android.hardware.camera2.params.StreamConfigurationDuration[]>("android.jpegr.availableJpegRMinFrameDurations", android.hardware.camera2.params.StreamConfigurationDuration[].class);
+
+ /**
+ * <p>This lists the maximum stall duration for each
+ * output format/size combination for Jpeg/R streams.</p>
+ * <p>A stall duration is how much extra time would get added
+ * to the normal minimum frame duration for a repeating request
+ * that has streams with non-zero stall.</p>
+ * <p>This functions similarly to
+ * android.scaler.availableStallDurations for Jpeg/R
+ * streams.</p>
+ * <p>All Jpeg/R output stream formats may have a nonzero stall
+ * duration.</p>
+ * <p><b>Units</b>: (format, width, height, ns) x n</p>
+ * <p><b>Optional</b> - The value for this key may be {@code null} on some devices.</p>
+ * <p><b>Limited capability</b> -
+ * Present on all camera devices that report being at least {@link CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL_LIMITED HARDWARE_LEVEL_LIMITED} devices in the
+ * {@link CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL android.info.supportedHardwareLevel} key</p>
+ *
+ * @see CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL
+ * @hide
+ */
+ public static final Key<android.hardware.camera2.params.StreamConfigurationDuration[]> JPEGR_AVAILABLE_JPEG_R_STALL_DURATIONS =
+ new Key<android.hardware.camera2.params.StreamConfigurationDuration[]>("android.jpegr.availableJpegRStallDurations", android.hardware.camera2.params.StreamConfigurationDuration[].class);
+
+ /**
+ * <p>The available Jpeg/R stream
+ * configurations that this camera device supports
+ * (i.e. format, width, height, output/input stream).</p>
+ * <p>Refer to android.jpegr.availableJpegRStreamConfigurations for details.</p>
+ * <p><b>Optional</b> - The value for this key may be {@code null} on some devices.</p>
+ * @hide
+ */
+ public static final Key<android.hardware.camera2.params.StreamConfiguration[]> JPEGR_AVAILABLE_JPEG_R_STREAM_CONFIGURATIONS_MAXIMUM_RESOLUTION =
+ new Key<android.hardware.camera2.params.StreamConfiguration[]>("android.jpegr.availableJpegRStreamConfigurationsMaximumResolution", android.hardware.camera2.params.StreamConfiguration[].class);
+
+ /**
+ * <p>This lists the minimum frame duration for each
+ * format/size combination for Jpeg/R output formats for CaptureRequests where
+ * {@link CaptureRequest#SENSOR_PIXEL_MODE android.sensor.pixelMode} is set to
+ * {@link android.hardware.camera2.CameraMetadata#SENSOR_PIXEL_MODE_MAXIMUM_RESOLUTION }.</p>
+ * <p>Refer to android.jpegr.availableJpegRMinFrameDurations for details.</p>
+ * <p><b>Units</b>: (format, width, height, ns) x n</p>
+ * <p><b>Optional</b> - The value for this key may be {@code null} on some devices.</p>
+ *
+ * @see CaptureRequest#SENSOR_PIXEL_MODE
+ * @hide
+ */
+ public static final Key<android.hardware.camera2.params.StreamConfigurationDuration[]> JPEGR_AVAILABLE_JPEG_R_MIN_FRAME_DURATIONS_MAXIMUM_RESOLUTION =
+ new Key<android.hardware.camera2.params.StreamConfigurationDuration[]>("android.jpegr.availableJpegRMinFrameDurationsMaximumResolution", android.hardware.camera2.params.StreamConfigurationDuration[].class);
+
+ /**
+ * <p>This lists the maximum stall duration for each
+ * output format/size combination for Jpeg/R streams for CaptureRequests where
+ * {@link CaptureRequest#SENSOR_PIXEL_MODE android.sensor.pixelMode} is set to
+ * {@link android.hardware.camera2.CameraMetadata#SENSOR_PIXEL_MODE_MAXIMUM_RESOLUTION }.</p>
+ * <p>Refer to android.jpegr.availableJpegRStallDurations for details.</p>
+ * <p><b>Units</b>: (format, width, height, ns) x n</p>
+ * <p><b>Optional</b> - The value for this key may be {@code null} on some devices.</p>
+ *
+ * @see CaptureRequest#SENSOR_PIXEL_MODE
+ * @hide
+ */
+ public static final Key<android.hardware.camera2.params.StreamConfigurationDuration[]> JPEGR_AVAILABLE_JPEG_R_STALL_DURATIONS_MAXIMUM_RESOLUTION =
+ new Key<android.hardware.camera2.params.StreamConfigurationDuration[]>("android.jpegr.availableJpegRStallDurationsMaximumResolution", android.hardware.camera2.params.StreamConfigurationDuration[].class);
+
/*~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~
* End generated code
*~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~O@*/
diff --git a/core/java/android/hardware/camera2/CameraDevice.java b/core/java/android/hardware/camera2/CameraDevice.java
index 10a7538..bf2e563 100644
--- a/core/java/android/hardware/camera2/CameraDevice.java
+++ b/core/java/android/hardware/camera2/CameraDevice.java
@@ -859,6 +859,28 @@
* format {@link android.graphics.ImageFormat#YUV_420_888} with a 10-bit profile
* will cause a capture session initialization failure.
* </p>
+ * <p>{@link android.graphics.ImageFormat#JPEG_R} may also be supported if advertised by
+ * {@link android.hardware.camera2.params.StreamConfigurationMap}. When initializing a capture
+ * session that includes a Jpeg/R camera output clients must consider the following items w.r.t.
+ * the 10-bit mandatory stream combination table:
+ *
+ * <ul>
+ * <li>To generate the compressed Jpeg/R image a single
+ * {@link android.graphics.ImageFormat#YCBCR_P010} output will be used internally by
+ * the camera device.</li>
+ * <li>On camera devices that are able to support concurrent 10 and 8-bit capture requests
+ * see {@link android.hardware.camera2.params.DynamicRangeProfiles#getProfileCaptureRequestConstraints}
+ * an extra {@link android.graphics.ImageFormat#JPEG} will also
+ * be configured internally to help speed up the encoding process.</li>
+ * </ul>
+ *
+ * Jpeg/R camera outputs will typically be able to support the MAXIMUM device resolution.
+ * Clients can also call {@link StreamConfigurationMap#getOutputSizes(int)} for a complete list
+ * supported sizes.
+ * Camera clients that register a Jpeg/R output within a stream combination that doesn't fit
+ * in the mandatory stream table above can call
+ * {@link CameraDevice#isSessionConfigurationSupported} to ensure that this particular
+ * configuration is supported.</p>
*
* <p>Devices with the STREAM_USE_CASE capability ({@link
* CameraCharacteristics#REQUEST_AVAILABLE_CAPABILITIES} includes {@link
diff --git a/core/java/android/hardware/camera2/impl/CameraMetadataNative.java b/core/java/android/hardware/camera2/impl/CameraMetadataNative.java
index 012fad5..9a16474 100644
--- a/core/java/android/hardware/camera2/impl/CameraMetadataNative.java
+++ b/core/java/android/hardware/camera2/impl/CameraMetadataNative.java
@@ -1351,6 +1351,9 @@
/*heicconfiguration*/ null,
/*heicminduration*/ null,
/*heicstallduration*/ null,
+ /*jpegRconfiguration*/ null,
+ /*jpegRminduration*/ null,
+ /*jpegRstallduration*/ null,
/*highspeedvideoconfigurations*/ null,
/*inputoutputformatsmap*/ null, listHighResolution, supportsPrivate[i]);
break;
@@ -1365,6 +1368,9 @@
/*heicconfiguration*/ null,
/*heicminduration*/ null,
/*heicstallduration*/ null,
+ /*jpegRconfiguration*/ null,
+ /*jpegRminduration*/ null,
+ /*jpegRstallduration*/ null,
highSpeedVideoConfigurations,
/*inputoutputformatsmap*/ null, listHighResolution, supportsPrivate[i]);
break;
@@ -1379,6 +1385,9 @@
/*heicconfiguration*/ null,
/*heicminduration*/ null,
/*heicstallduration*/ null,
+ /*jpegRconfiguration*/ null,
+ /*jpegRminduration*/ null,
+ /*jpegRstallduration*/ null,
/*highSpeedVideoConfigurations*/ null,
inputOutputFormatsMap, listHighResolution, supportsPrivate[i]);
break;
@@ -1393,6 +1402,9 @@
/*heicconfiguration*/ null,
/*heicminduration*/ null,
/*heicstallduration*/ null,
+ /*jpegRconfiguration*/ null,
+ /*jpegRminduration*/ null,
+ /*jpegRstallduration*/ null,
/*highSpeedVideoConfigurations*/ null,
/*inputOutputFormatsMap*/ null, listHighResolution, supportsPrivate[i]);
}
@@ -1546,6 +1558,12 @@
CameraCharacteristics.HEIC_AVAILABLE_HEIC_MIN_FRAME_DURATIONS);
StreamConfigurationDuration[] heicStallDurations = getBase(
CameraCharacteristics.HEIC_AVAILABLE_HEIC_STALL_DURATIONS);
+ StreamConfiguration[] jpegRConfigurations = getBase(
+ CameraCharacteristics.JPEGR_AVAILABLE_JPEG_R_STREAM_CONFIGURATIONS);
+ StreamConfigurationDuration[] jpegRMinFrameDurations = getBase(
+ CameraCharacteristics.JPEGR_AVAILABLE_JPEG_R_MIN_FRAME_DURATIONS);
+ StreamConfigurationDuration[] jpegRStallDurations = getBase(
+ CameraCharacteristics.JPEGR_AVAILABLE_JPEG_R_STALL_DURATIONS);
HighSpeedVideoConfiguration[] highSpeedVideoConfigurations = getBase(
CameraCharacteristics.CONTROL_AVAILABLE_HIGH_SPEED_VIDEO_CONFIGURATIONS);
ReprocessFormatsMap inputOutputFormatsMap = getBase(
@@ -1557,6 +1575,7 @@
dynamicDepthConfigurations, dynamicDepthMinFrameDurations,
dynamicDepthStallDurations, heicConfigurations,
heicMinFrameDurations, heicStallDurations,
+ jpegRConfigurations, jpegRMinFrameDurations, jpegRStallDurations,
highSpeedVideoConfigurations, inputOutputFormatsMap,
listHighResolution);
}
@@ -1589,6 +1608,12 @@
CameraCharacteristics.HEIC_AVAILABLE_HEIC_MIN_FRAME_DURATIONS_MAXIMUM_RESOLUTION);
StreamConfigurationDuration[] heicStallDurations = getBase(
CameraCharacteristics.HEIC_AVAILABLE_HEIC_STALL_DURATIONS_MAXIMUM_RESOLUTION);
+ StreamConfiguration[] jpegRConfigurations = getBase(
+ CameraCharacteristics.JPEGR_AVAILABLE_JPEG_R_STREAM_CONFIGURATIONS_MAXIMUM_RESOLUTION);
+ StreamConfigurationDuration[] jpegRMinFrameDurations = getBase(
+ CameraCharacteristics.JPEGR_AVAILABLE_JPEG_R_MIN_FRAME_DURATIONS_MAXIMUM_RESOLUTION);
+ StreamConfigurationDuration[] jpegRStallDurations = getBase(
+ CameraCharacteristics.JPEGR_AVAILABLE_JPEG_R_STALL_DURATIONS_MAXIMUM_RESOLUTION);
HighSpeedVideoConfiguration[] highSpeedVideoConfigurations = getBase(
CameraCharacteristics.CONTROL_AVAILABLE_HIGH_SPEED_VIDEO_CONFIGURATIONS_MAXIMUM_RESOLUTION);
ReprocessFormatsMap inputOutputFormatsMap = getBase(
@@ -1601,6 +1626,7 @@
dynamicDepthConfigurations, dynamicDepthMinFrameDurations,
dynamicDepthStallDurations, heicConfigurations,
heicMinFrameDurations, heicStallDurations,
+ jpegRConfigurations, jpegRMinFrameDurations, jpegRStallDurations,
highSpeedVideoConfigurations, inputOutputFormatsMap,
listHighResolution, false);
}
diff --git a/core/java/android/hardware/camera2/params/StreamConfigurationMap.java b/core/java/android/hardware/camera2/params/StreamConfigurationMap.java
index 5981d27..cb678b9 100644
--- a/core/java/android/hardware/camera2/params/StreamConfigurationMap.java
+++ b/core/java/android/hardware/camera2/params/StreamConfigurationMap.java
@@ -26,6 +26,7 @@
import android.hardware.camera2.CaptureRequest;
import android.hardware.camera2.utils.HashCodeHelpers;
import android.hardware.camera2.utils.SurfaceUtils;
+import android.util.Log;
import android.util.Range;
import android.util.Size;
import android.util.SparseIntArray;
@@ -95,6 +96,11 @@
* {@link StreamConfigurationDuration}
* @param heicStallDurations a non-{@code null} array of heic
* {@link StreamConfigurationDuration}
+ * @param jpegRConfigurations a non-{@code null} array of Jpeg/R {@link StreamConfiguration}
+ * @param jpegRMinFrameDurations a non-{@code null} array of Jpeg/R
+ * {@link StreamConfigurationDuration}
+ * @param jpegRStallDurations a non-{@code null} array of Jpeg/R
+ * {@link StreamConfigurationDuration}
* @param highSpeedVideoConfigurations an array of {@link HighSpeedVideoConfiguration}, null if
* camera device does not support high speed video recording
* @param listHighResolution a flag indicating whether the device supports BURST_CAPTURE
@@ -117,6 +123,9 @@
StreamConfiguration[] heicConfigurations,
StreamConfigurationDuration[] heicMinFrameDurations,
StreamConfigurationDuration[] heicStallDurations,
+ StreamConfiguration[] jpegRConfigurations,
+ StreamConfigurationDuration[] jpegRMinFrameDurations,
+ StreamConfigurationDuration[] jpegRStallDurations,
HighSpeedVideoConfiguration[] highSpeedVideoConfigurations,
ReprocessFormatsMap inputOutputFormatsMap,
boolean listHighResolution) {
@@ -125,6 +134,7 @@
dynamicDepthConfigurations, dynamicDepthMinFrameDurations,
dynamicDepthStallDurations,
heicConfigurations, heicMinFrameDurations, heicStallDurations,
+ jpegRConfigurations, jpegRMinFrameDurations, jpegRStallDurations,
highSpeedVideoConfigurations, inputOutputFormatsMap, listHighResolution,
/*enforceImplementationDefined*/ true);
}
@@ -154,6 +164,11 @@
* {@link StreamConfigurationDuration}
* @param heicStallDurations a non-{@code null} array of heic
* {@link StreamConfigurationDuration}
+ * @param jpegRConfigurations a non-{@code null} array of Jpeg/R {@link StreamConfiguration}
+ * @param jpegRMinFrameDurations a non-{@code null} array of Jpeg/R
+ * {@link StreamConfigurationDuration}
+ * @param jpegRStallDurations a non-{@code null} array of Jpeg/R
+ * {@link StreamConfigurationDuration}
* @param highSpeedVideoConfigurations an array of {@link HighSpeedVideoConfiguration}, null if
* camera device does not support high speed video recording
* @param listHighResolution a flag indicating whether the device supports BURST_CAPTURE
@@ -178,6 +193,9 @@
StreamConfiguration[] heicConfigurations,
StreamConfigurationDuration[] heicMinFrameDurations,
StreamConfigurationDuration[] heicStallDurations,
+ StreamConfiguration[] jpegRConfigurations,
+ StreamConfigurationDuration[] jpegRMinFrameDurations,
+ StreamConfigurationDuration[] jpegRStallDurations,
HighSpeedVideoConfiguration[] highSpeedVideoConfigurations,
ReprocessFormatsMap inputOutputFormatsMap,
boolean listHighResolution,
@@ -242,6 +260,20 @@
"heicStallDurations");
}
+
+ if (jpegRConfigurations == null) {
+ mJpegRConfigurations = new StreamConfiguration[0];
+ mJpegRMinFrameDurations = new StreamConfigurationDuration[0];
+ mJpegRStallDurations = new StreamConfigurationDuration[0];
+ } else {
+ mJpegRConfigurations = checkArrayElementsNotNull(jpegRConfigurations,
+ "jpegRConfigurations");
+ mJpegRMinFrameDurations = checkArrayElementsNotNull(jpegRMinFrameDurations,
+ "jpegRFrameDurations");
+ mJpegRStallDurations = checkArrayElementsNotNull(jpegRStallDurations,
+ "jpegRStallDurations");
+ }
+
if (highSpeedVideoConfigurations == null) {
mHighSpeedVideoConfigurations = new HighSpeedVideoConfiguration[0];
} else {
@@ -305,6 +337,17 @@
mHeicOutputFormats.get(config.getFormat()) + 1);
}
+ // For each Jpeg/R format, track how many sizes there are available to configure
+ for (StreamConfiguration config : mJpegRConfigurations) {
+ if (!config.isOutput()) {
+ // Ignoring input Jpeg/R configs
+ continue;
+ }
+
+ mJpegROutputFormats.put(config.getFormat(),
+ mJpegROutputFormats.get(config.getFormat()) + 1);
+ }
+
if (configurations != null && enforceImplementationDefined &&
mOutputFormats.indexOfKey(HAL_PIXEL_FORMAT_IMPLEMENTATION_DEFINED) < 0) {
throw new AssertionError(
@@ -447,6 +490,8 @@
return mDynamicDepthOutputFormats.indexOfKey(internalFormat) >= 0;
} else if (dataspace == HAL_DATASPACE_HEIF) {
return mHeicOutputFormats.indexOfKey(internalFormat) >= 0;
+ } else if (dataspace == HAL_DATASPACE_JPEG_R) {
+ return mJpegROutputFormats.indexOfKey(internalFormat) >= 0;
} else {
return getFormatsMap(/*output*/true).indexOfKey(internalFormat) >= 0;
}
@@ -561,6 +606,7 @@
surfaceDataspace == HAL_DATASPACE_DEPTH ? mDepthConfigurations :
surfaceDataspace == HAL_DATASPACE_DYNAMIC_DEPTH ? mDynamicDepthConfigurations :
surfaceDataspace == HAL_DATASPACE_HEIF ? mHeicConfigurations :
+ surfaceDataspace == HAL_DATASPACE_JPEG_R ? mJpegRConfigurations :
mConfigurations;
for (StreamConfiguration config : configs) {
if (config.getFormat() == surfaceFormat && config.isOutput()) {
@@ -597,6 +643,7 @@
dataspace == HAL_DATASPACE_DEPTH ? mDepthConfigurations :
dataspace == HAL_DATASPACE_DYNAMIC_DEPTH ? mDynamicDepthConfigurations :
dataspace == HAL_DATASPACE_HEIF ? mHeicConfigurations :
+ dataspace == HAL_DATASPACE_JPEG_R ? mJpegRConfigurations :
mConfigurations;
for (StreamConfiguration config : configs) {
if ((config.getFormat() == internalFormat) && config.isOutput() &&
@@ -1120,6 +1167,9 @@
Arrays.equals(mHeicConfigurations, other.mHeicConfigurations) &&
Arrays.equals(mHeicMinFrameDurations, other.mHeicMinFrameDurations) &&
Arrays.equals(mHeicStallDurations, other.mHeicStallDurations) &&
+ Arrays.equals(mJpegRConfigurations, other.mJpegRConfigurations) &&
+ Arrays.equals(mJpegRMinFrameDurations, other.mJpegRMinFrameDurations) &&
+ Arrays.equals(mJpegRStallDurations, other.mJpegRStallDurations) &&
Arrays.equals(mHighSpeedVideoConfigurations,
other.mHighSpeedVideoConfigurations);
}
@@ -1138,6 +1188,7 @@
mDynamicDepthConfigurations, mDynamicDepthMinFrameDurations,
mDynamicDepthStallDurations, mHeicConfigurations,
mHeicMinFrameDurations, mHeicStallDurations,
+ mJpegRConfigurations, mJpegRMinFrameDurations, mJpegRStallDurations,
mHighSpeedVideoConfigurations);
}
@@ -1161,6 +1212,10 @@
if (mHeicOutputFormats.indexOfKey(internalFormat) >= 0) {
return format;
}
+ } else if (internalDataspace == HAL_DATASPACE_JPEG_R) {
+ if (mJpegROutputFormats.indexOfKey(internalFormat) >= 0) {
+ return format;
+ }
} else {
if (mAllOutputFormats.indexOfKey(internalFormat) >= 0) {
return format;
@@ -1365,6 +1420,7 @@
* <li>ImageFormat.DEPTH_POINT_CLOUD => HAL_PIXEL_FORMAT_BLOB
* <li>ImageFormat.DEPTH_JPEG => HAL_PIXEL_FORMAT_BLOB
* <li>ImageFormat.HEIC => HAL_PIXEL_FORMAT_BLOB
+ * <li>ImageFormat.JPEG_R => HAL_PIXEL_FORMAT_BLOB
* <li>ImageFormat.DEPTH16 => HAL_PIXEL_FORMAT_Y16
* </ul>
* </p>
@@ -1391,6 +1447,7 @@
case ImageFormat.DEPTH_POINT_CLOUD:
case ImageFormat.DEPTH_JPEG:
case ImageFormat.HEIC:
+ case ImageFormat.JPEG_R:
return HAL_PIXEL_FORMAT_BLOB;
case ImageFormat.DEPTH16:
return HAL_PIXEL_FORMAT_Y16;
@@ -1414,6 +1471,7 @@
* <li>ImageFormat.DEPTH16 => HAL_DATASPACE_DEPTH
* <li>ImageFormat.DEPTH_JPEG => HAL_DATASPACE_DYNAMIC_DEPTH
* <li>ImageFormat.HEIC => HAL_DATASPACE_HEIF
+ * <li>ImageFormat.JPEG_R => HAL_DATASPACE_JPEG_R
* <li>others => HAL_DATASPACE_UNKNOWN
* </ul>
* </p>
@@ -1448,6 +1506,8 @@
return HAL_DATASPACE_DYNAMIC_DEPTH;
case ImageFormat.HEIC:
return HAL_DATASPACE_HEIF;
+ case ImageFormat.JPEG_R:
+ return HAL_DATASPACE_JPEG_R;
default:
return HAL_DATASPACE_UNKNOWN;
}
@@ -1500,14 +1560,15 @@
dataspace == HAL_DATASPACE_DEPTH ? mDepthOutputFormats :
dataspace == HAL_DATASPACE_DYNAMIC_DEPTH ? mDynamicDepthOutputFormats :
dataspace == HAL_DATASPACE_HEIF ? mHeicOutputFormats :
+ dataspace == HAL_DATASPACE_JPEG_R ? mJpegROutputFormats :
highRes ? mHighResOutputFormats :
mOutputFormats;
int sizesCount = formatsMap.get(format);
- if ( ((!output || (dataspace == HAL_DATASPACE_DEPTH ||
+ if ( ((!output || (dataspace == HAL_DATASPACE_DEPTH || dataspace == HAL_DATASPACE_JPEG_R ||
dataspace == HAL_DATASPACE_DYNAMIC_DEPTH ||
dataspace == HAL_DATASPACE_HEIF)) && sizesCount == 0) ||
- (output && (dataspace != HAL_DATASPACE_DEPTH &&
+ (output && (dataspace != HAL_DATASPACE_DEPTH && dataspace != HAL_DATASPACE_JPEG_R &&
dataspace != HAL_DATASPACE_DYNAMIC_DEPTH &&
dataspace != HAL_DATASPACE_HEIF) &&
mAllOutputFormats.get(format) == 0)) {
@@ -1521,11 +1582,13 @@
(dataspace == HAL_DATASPACE_DEPTH) ? mDepthConfigurations :
(dataspace == HAL_DATASPACE_DYNAMIC_DEPTH) ? mDynamicDepthConfigurations :
(dataspace == HAL_DATASPACE_HEIF) ? mHeicConfigurations :
+ (dataspace == HAL_DATASPACE_JPEG_R) ? mJpegRConfigurations :
mConfigurations;
StreamConfigurationDuration[] minFrameDurations =
(dataspace == HAL_DATASPACE_DEPTH) ? mDepthMinFrameDurations :
(dataspace == HAL_DATASPACE_DYNAMIC_DEPTH) ? mDynamicDepthMinFrameDurations :
(dataspace == HAL_DATASPACE_HEIF) ? mHeicMinFrameDurations :
+ (dataspace == HAL_DATASPACE_JPEG_R) ? mJpegRMinFrameDurations :
mMinFrameDurations;
for (StreamConfiguration config : configurations) {
@@ -1555,7 +1618,7 @@
// Dynamic depth streams can have both fast and also high res modes.
if ((sizeIndex != sizesCount) && (dataspace == HAL_DATASPACE_DYNAMIC_DEPTH ||
- dataspace == HAL_DATASPACE_HEIF)) {
+ dataspace == HAL_DATASPACE_HEIF) || (dataspace == HAL_DATASPACE_JPEG_R)) {
if (sizeIndex > sizesCount) {
throw new AssertionError(
@@ -1598,6 +1661,9 @@
if (mHeicOutputFormats.size() > 0) {
formats[i++] = ImageFormat.HEIC;
}
+ if (mJpegROutputFormats.size() > 0) {
+ formats[i++] = ImageFormat.JPEG_R;
+ }
}
if (formats.length != i) {
throw new AssertionError("Too few formats " + i + ", expected " + formats.length);
@@ -1644,12 +1710,14 @@
(dataspace == HAL_DATASPACE_DYNAMIC_DEPTH) ?
mDynamicDepthMinFrameDurations :
(dataspace == HAL_DATASPACE_HEIF) ? mHeicMinFrameDurations :
+ (dataspace == HAL_DATASPACE_JPEG_R) ? mJpegRMinFrameDurations :
mMinFrameDurations;
case DURATION_STALL:
return (dataspace == HAL_DATASPACE_DEPTH) ? mDepthStallDurations :
(dataspace == HAL_DATASPACE_DYNAMIC_DEPTH) ? mDynamicDepthStallDurations :
(dataspace == HAL_DATASPACE_HEIF) ? mHeicStallDurations :
+ (dataspace == HAL_DATASPACE_JPEG_R) ? mJpegRStallDurations :
mStallDurations;
default:
throw new IllegalArgumentException("duration was invalid");
@@ -1664,6 +1732,7 @@
size += mDepthOutputFormats.size();
size += mDynamicDepthOutputFormats.size();
size += mHeicOutputFormats.size();
+ size += mJpegROutputFormats.size();
}
return size;
@@ -1688,6 +1757,7 @@
(dataspace == HAL_DATASPACE_DEPTH) ? mDepthConfigurations :
(dataspace == HAL_DATASPACE_DYNAMIC_DEPTH) ? mDynamicDepthConfigurations :
(dataspace == HAL_DATASPACE_HEIF) ? mHeicConfigurations :
+ (dataspace == HAL_DATASPACE_JPEG_R) ? mJpegRConfigurations :
mConfigurations;
for (int i = 0; i < configurations.length; i++) {
@@ -1908,6 +1978,8 @@
return "PRIVATE";
case ImageFormat.HEIC:
return "HEIC";
+ case ImageFormat.JPEG_R:
+ return "JPEG/R";
default:
return "UNKNOWN";
}
@@ -1948,6 +2020,10 @@
* @hide
*/
public static final int HAL_DATASPACE_HEIF = 0x1003;
+ /**
+ * @hide
+ */
+ public static final int HAL_DATASPACE_JPEG_R = 0x1005;
private static final long DURATION_20FPS_NS = 50000000L;
/**
* @see #getDurations(int, int)
@@ -1971,6 +2047,10 @@
private final StreamConfigurationDuration[] mHeicMinFrameDurations;
private final StreamConfigurationDuration[] mHeicStallDurations;
+ private final StreamConfiguration[] mJpegRConfigurations;
+ private final StreamConfigurationDuration[] mJpegRMinFrameDurations;
+ private final StreamConfigurationDuration[] mJpegRStallDurations;
+
private final HighSpeedVideoConfiguration[] mHighSpeedVideoConfigurations;
private final ReprocessFormatsMap mInputOutputFormatsMap;
@@ -1992,6 +2072,8 @@
private final SparseIntArray mDynamicDepthOutputFormats = new SparseIntArray();
/** internal format -> num heic output sizes mapping, for HAL_DATASPACE_HEIF */
private final SparseIntArray mHeicOutputFormats = new SparseIntArray();
+ /** internal format -> num Jpeg/R output sizes mapping, for HAL_DATASPACE_JPEG_R */
+ private final SparseIntArray mJpegROutputFormats = new SparseIntArray();
/** High speed video Size -> FPS range count mapping*/
private final HashMap</*HighSpeedVideoSize*/Size, /*Count*/Integer> mHighSpeedVideoSizeMap =
diff --git a/core/java/android/os/PatternMatcher.java b/core/java/android/os/PatternMatcher.java
index 631c98a..b5425b4 100644
--- a/core/java/android/os/PatternMatcher.java
+++ b/core/java/android/os/PatternMatcher.java
@@ -16,6 +16,7 @@
package android.os;
+import android.util.Log;
import android.util.proto.ProtoOutputStream;
import java.util.Arrays;
@@ -151,6 +152,23 @@
proto.end(token);
}
+ /**
+ * Perform a check on the matcher for the pattern type of {@link #PATTERN_ADVANCED_GLOB}.
+ * Return true if it passed.
+ * @hide
+ */
+ public boolean check() {
+ try {
+ if (mType == PATTERN_ADVANCED_GLOB) {
+ return Arrays.equals(mParsedPattern, parseAndVerifyAdvancedPattern(mPattern));
+ }
+ } catch (IllegalArgumentException e) {
+ Log.w(TAG, "Failed to verify advanced pattern: " + e.getMessage());
+ return false;
+ }
+ return true;
+ }
+
public int describeContents() {
return 0;
}
diff --git a/core/java/android/security/rkp/IRemoteProvisioning.aidl b/core/java/android/security/rkp/IRemoteProvisioning.aidl
index 23d8159..0efaa89 100644
--- a/core/java/android/security/rkp/IRemoteProvisioning.aidl
+++ b/core/java/android/security/rkp/IRemoteProvisioning.aidl
@@ -58,11 +58,4 @@
*
*/
void getRegistration(String irpcName, IGetRegistrationCallback callback);
-
- /**
- * Cancel any active {@link getRegistration} call associated with the given
- * callback. If no getRegistration call is currently active, this function is
- * a noop.
- */
- void cancelGetRegistration(IGetRegistrationCallback callback);
}
diff --git a/core/java/android/service/credentials/ClearCredentialStateRequest.aidl b/core/java/android/service/credentials/ClearCredentialStateRequest.aidl
new file mode 100644
index 0000000..f4b45c2
--- /dev/null
+++ b/core/java/android/service/credentials/ClearCredentialStateRequest.aidl
@@ -0,0 +1,3 @@
+package android.service.credentials;
+
+parcelable ClearCredentialStateRequest;
\ No newline at end of file
diff --git a/core/java/android/service/credentials/ClearCredentialStateRequest.java b/core/java/android/service/credentials/ClearCredentialStateRequest.java
new file mode 100644
index 0000000..57189bb
--- /dev/null
+++ b/core/java/android/service/credentials/ClearCredentialStateRequest.java
@@ -0,0 +1,100 @@
+/*
+ * Copyright 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.service.credentials;
+
+import static java.util.Objects.requireNonNull;
+
+import android.annotation.NonNull;
+import android.os.Bundle;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import com.android.internal.util.AnnotationValidations;
+/**
+ * A request class for clearing a user's credential state.
+ * Providers must clear the credential state when they receive this request.
+ */
+public final class ClearCredentialStateRequest implements Parcelable {
+ /** The request data. */
+ @NonNull
+ private final CallingAppInfo mCallingAppInfo;
+
+ /** The request data. */
+ @NonNull
+ private final Bundle mData;
+
+ /** Returns the request data. */
+ @NonNull
+ public Bundle getData() {
+ return mData;
+ }
+
+ /** Returns the calling app info containing information pertaining to the calling app. */
+ @NonNull
+ public CallingAppInfo getCallingAppInfo() {
+ return mCallingAppInfo;
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ dest.writeTypedObject(mCallingAppInfo, flags);
+ dest.writeBundle(mData);
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public String toString() {
+ return "ClearCredentialStateRequest {callingAppInfo="
+ + mCallingAppInfo.toString() + " }, {data= " + mData + "}";
+ }
+
+ /**
+ * Constructs a {@link ClearCredentialStateRequest}.
+ *
+ * @param data the request data
+ */
+ public ClearCredentialStateRequest(@NonNull CallingAppInfo callingAppInfo,
+ @NonNull Bundle data) {
+ mCallingAppInfo = requireNonNull(
+ callingAppInfo, "callingAppInfo must not be null");
+ mData = requireNonNull(data, "data must not be null");
+ }
+
+ private ClearCredentialStateRequest(@NonNull Parcel in) {
+ mCallingAppInfo = in.readTypedObject(CallingAppInfo.CREATOR);
+ Bundle data = in.readBundle();
+ mData = data;
+ AnnotationValidations.validate(NonNull.class, null, mData);
+ }
+
+ public static final @NonNull Creator<ClearCredentialStateRequest> CREATOR =
+ new Creator<ClearCredentialStateRequest>() {
+ @Override
+ public ClearCredentialStateRequest[] newArray(int size) {
+ return new ClearCredentialStateRequest[size];
+ }
+
+ @Override
+ public ClearCredentialStateRequest createFromParcel(@NonNull Parcel in) {
+ return new ClearCredentialStateRequest(in);
+ }
+ };
+}
diff --git a/core/java/android/service/credentials/CredentialProviderErrors.java b/core/java/android/service/credentials/CredentialProviderErrors.java
new file mode 100644
index 0000000..e9dc35b
--- /dev/null
+++ b/core/java/android/service/credentials/CredentialProviderErrors.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.service.credentials;
+
+/**
+ * Contains custom error codes to be used internally for various credential
+ * provider error states.
+ *
+ * @hide
+ */
+public class CredentialProviderErrors {
+ public static final int ERROR_UNKNOWN = 0;
+
+ /**
+ * For internal use only.
+ * Error code to be used when the provider request times out.
+ *
+ * @hide
+ */
+ public static final int ERROR_TIMEOUT = 1;
+
+ /**
+ * For internal use only.
+ * Error code to be used when the async task is canceled internally.
+ *
+ * @hide
+ */
+ public static final int ERROR_TASK_CANCELED = 2;
+
+ /**
+ * For internal use only.
+ * Error code to be used when an exception is received from the provider.
+ *
+ * @hide
+ */
+ public static final int ERROR_PROVIDER_FAILURE = 3;
+}
diff --git a/core/java/android/service/credentials/CredentialProviderException.java b/core/java/android/service/credentials/CredentialProviderException.java
deleted file mode 100644
index 969bcb5..0000000
--- a/core/java/android/service/credentials/CredentialProviderException.java
+++ /dev/null
@@ -1,96 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.service.credentials;
-
-import android.annotation.IntDef;
-import android.annotation.NonNull;
-
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-
-/**
- * Contains custom exceptions to be used by credential providers on failure.
- *
- * @hide
- */
-public class CredentialProviderException extends Exception {
- public static final int ERROR_UNKNOWN = 0;
-
- /**
- * For internal use only.
- * Error code to be used when the provider request times out.
- *
- * @hide
- */
- public static final int ERROR_TIMEOUT = 1;
-
- /**
- * For internal use only.
- * Error code to be used when the async task is canceled internally.
- *
- * @hide
- */
- public static final int ERROR_TASK_CANCELED = 2;
-
- /**
- * For internal use only.
- * Error code to be used when the provider encounters a failure while processing the request.
- *
- * @hide
- */
- public static final int ERROR_PROVIDER_FAILURE = 3;
-
- private final int mErrorCode;
-
- /**
- * @hide
- */
- @IntDef(prefix = {"ERROR_"}, value = {
- ERROR_UNKNOWN,
- ERROR_TIMEOUT,
- ERROR_TASK_CANCELED
- })
- @Retention(RetentionPolicy.SOURCE)
- public @interface CredentialProviderError { }
-
- public CredentialProviderException(@CredentialProviderError int errorCode,
- @NonNull String message, @NonNull Throwable cause) {
- super(message, cause);
- mErrorCode = errorCode;
- }
-
- public CredentialProviderException(@CredentialProviderError int errorCode,
- @NonNull String message) {
- super(message);
- mErrorCode = errorCode;
- }
-
- public CredentialProviderException(@CredentialProviderError int errorCode,
- @NonNull Throwable cause) {
- super(cause);
- mErrorCode = errorCode;
- }
-
- public CredentialProviderException(@CredentialProviderError int errorCode) {
- super();
- mErrorCode = errorCode;
- }
-
- public @CredentialProviderError int getErrorCode() {
- return mErrorCode;
- }
-}
diff --git a/core/java/android/service/credentials/CredentialProviderService.java b/core/java/android/service/credentials/CredentialProviderService.java
index 7735e4c..41d20f2 100644
--- a/core/java/android/service/credentials/CredentialProviderService.java
+++ b/core/java/android/service/credentials/CredentialProviderService.java
@@ -24,6 +24,7 @@
import android.app.PendingIntent;
import android.app.Service;
import android.content.Intent;
+import android.credentials.ClearCredentialStateException;
import android.credentials.CreateCredentialException;
import android.credentials.GetCredentialException;
import android.os.CancellationSignal;
@@ -224,6 +225,40 @@
));
return transport;
}
+
+ @Override
+ public ICancellationSignal onClearCredentialState(ClearCredentialStateRequest request,
+ IClearCredentialStateCallback callback) {
+ Objects.requireNonNull(request);
+ Objects.requireNonNull(callback);
+
+ ICancellationSignal transport = CancellationSignal.createTransport();
+
+ mHandler.sendMessage(obtainMessage(
+ CredentialProviderService::onClearCredentialState,
+ CredentialProviderService.this, request,
+ CancellationSignal.fromTransport(transport),
+ new OutcomeReceiver<Void, ClearCredentialStateException>() {
+ @Override
+ public void onResult(Void result) {
+ try {
+ callback.onSuccess();
+ } catch (RemoteException e) {
+ e.rethrowFromSystemServer();
+ }
+ }
+ @Override
+ public void onError(ClearCredentialStateException e) {
+ try {
+ callback.onFailure(e.errorType, e.getMessage());
+ } catch (RemoteException ex) {
+ ex.rethrowFromSystemServer();
+ }
+ }
+ }
+ ));
+ return transport;
+ }
};
/**
@@ -262,4 +297,26 @@
@NonNull CancellationSignal cancellationSignal,
@NonNull OutcomeReceiver<BeginCreateCredentialResponse,
CreateCredentialException> callback);
+
+ /**
+ * Called by the android system to clear the credential state.
+ *
+ * This api isinvoked by developers after users sign out of an app, with an intention to
+ * clear any stored credential session that providers have retained.
+ *
+ * As a provider, you must clear any credential state, if maintained. For e.g. a provider may
+ * have stored an active credential session that is used to limit or rank sign-in options for
+ * future credential retrieval flows. When a user signs out of the app, such state should be
+ * cleared and an exhaustive list of credentials must be presented to the user on subsequent
+ * credential retrieval flows.
+ *
+ * @param request The clear credential request for the provider to handle.
+ * @param cancellationSignal Signal for providers to listen to any cancellation requests from
+ * the android system.
+ * @param callback Object used to relay the result of the request.
+ */
+ public abstract void onClearCredentialState(@NonNull ClearCredentialStateRequest request,
+ @NonNull CancellationSignal cancellationSignal,
+ @NonNull OutcomeReceiver<Void,
+ ClearCredentialStateException> callback);
}
diff --git a/core/java/android/service/credentials/IClearCredentialStateCallback.aidl b/core/java/android/service/credentials/IClearCredentialStateCallback.aidl
new file mode 100644
index 0000000..ec805d0
--- /dev/null
+++ b/core/java/android/service/credentials/IClearCredentialStateCallback.aidl
@@ -0,0 +1,27 @@
+/*
+ * Copyright 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.service.credentials;
+
+/**
+ * Callback for onClearCredentialState request.
+ *
+ * @hide
+ */
+interface IClearCredentialStateCallback {
+ oneway void onSuccess();
+ oneway void onFailure(String errorType, CharSequence message);
+}
\ No newline at end of file
diff --git a/core/java/android/service/credentials/ICredentialProviderService.aidl b/core/java/android/service/credentials/ICredentialProviderService.aidl
index 24d253f..626dd78 100644
--- a/core/java/android/service/credentials/ICredentialProviderService.aidl
+++ b/core/java/android/service/credentials/ICredentialProviderService.aidl
@@ -20,7 +20,9 @@
import android.service.credentials.BeginGetCredentialRequest;
import android.service.credentials.BeginCreateCredentialRequest;
import android.service.credentials.IBeginGetCredentialCallback;
+import android.service.credentials.ClearCredentialStateRequest;
import android.service.credentials.IBeginCreateCredentialCallback;
+import android.service.credentials.IClearCredentialStateCallback;
import android.os.ICancellationSignal;
/**
@@ -31,4 +33,5 @@
interface ICredentialProviderService {
ICancellationSignal onBeginGetCredential(in BeginGetCredentialRequest request, in IBeginGetCredentialCallback callback);
ICancellationSignal onBeginCreateCredential(in BeginCreateCredentialRequest request, in IBeginCreateCredentialCallback callback);
+ ICancellationSignal onClearCredentialState(in ClearCredentialStateRequest request, in IClearCredentialStateCallback callback);
}
diff --git a/core/java/android/service/quicksettings/IQSService.aidl b/core/java/android/service/quicksettings/IQSService.aidl
index d03ff93..7b690ea 100644
--- a/core/java/android/service/quicksettings/IQSService.aidl
+++ b/core/java/android/service/quicksettings/IQSService.aidl
@@ -15,6 +15,7 @@
*/
package android.service.quicksettings;
+import android.app.PendingIntent;
import android.content.ComponentName;
import android.graphics.drawable.Icon;
import android.service.quicksettings.Tile;
@@ -29,10 +30,10 @@
String contentDescription);
void onShowDialog(in IBinder tile);
void onStartActivity(in IBinder tile);
+ void startActivity(in IBinder tile, in PendingIntent pendingIntent);
boolean isLocked();
boolean isSecure();
void startUnlockAndRun(in IBinder tile);
-
void onDialogHidden(in IBinder tile);
void onStartSuccessful(in IBinder tile);
}
diff --git a/core/java/android/service/quicksettings/Tile.java b/core/java/android/service/quicksettings/Tile.java
index 40c0ac0..289b0e0 100644
--- a/core/java/android/service/quicksettings/Tile.java
+++ b/core/java/android/service/quicksettings/Tile.java
@@ -16,6 +16,7 @@
package android.service.quicksettings;
import android.annotation.Nullable;
+import android.app.PendingIntent;
import android.graphics.drawable.Icon;
import android.os.IBinder;
import android.os.Parcel;
@@ -39,8 +40,8 @@
/**
* An unavailable state indicates that for some reason this tile is not currently
- * available to the user for some reason, and will have no click action. The tile's
- * icon will be tinted differently to reflect this state.
+ * available to the user, and will have no click action. The tile's icon will be
+ * tinted differently to reflect this state.
*/
public static final int STATE_UNAVAILABLE = 0;
@@ -66,6 +67,7 @@
private CharSequence mSubtitle;
private CharSequence mContentDescription;
private CharSequence mStateDescription;
+ private PendingIntent mPendingIntent;
// Default to inactive until clients of the new API can update.
private int mState = STATE_INACTIVE;
@@ -223,6 +225,34 @@
}
}
+ /**
+ * Gets the Activity {@link PendingIntent} to be launched when the tile is clicked.
+ * @hide
+ */
+ @Nullable
+ public PendingIntent getActivityLaunchForClick() {
+ return mPendingIntent;
+ }
+
+ /**
+ * Sets an Activity {@link PendingIntent} to be launched when the tile is clicked.
+ *
+ * The last value set here will be launched when the user clicks in the tile, instead of
+ * forwarding the `onClick` message to the {@link TileService}. Set to {@code null} to handle
+ * the `onClick` in the `TileService`
+ * (This is the default behavior if this method is never called.)
+ * @param pendingIntent a PendingIntent for an activity to be launched onclick, or {@code null}
+ * to handle the clicks in the `TileService`.
+ * @hide
+ */
+ public void setActivityLaunchForClick(@Nullable PendingIntent pendingIntent) {
+ if (pendingIntent != null && !pendingIntent.isActivity()) {
+ throw new IllegalArgumentException();
+ } else {
+ mPendingIntent = pendingIntent;
+ }
+ }
+
@Override
public void writeToParcel(Parcel dest, int flags) {
if (mIcon != null) {
@@ -231,6 +261,12 @@
} else {
dest.writeByte((byte) 0);
}
+ if (mPendingIntent != null) {
+ dest.writeByte((byte) 1);
+ mPendingIntent.writeToParcel(dest, flags);
+ } else {
+ dest.writeByte((byte) 0);
+ }
dest.writeInt(mState);
TextUtils.writeToParcel(mLabel, dest, flags);
TextUtils.writeToParcel(mSubtitle, dest, flags);
@@ -244,6 +280,11 @@
} else {
mIcon = null;
}
+ if (source.readByte() != 0) {
+ mPendingIntent = PendingIntent.CREATOR.createFromParcel(source);
+ } else {
+ mPendingIntent = null;
+ }
mState = source.readInt();
mLabel = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(source);
mSubtitle = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(source);
diff --git a/core/java/android/service/quicksettings/TileService.java b/core/java/android/service/quicksettings/TileService.java
index 8550219..506b3b8 100644
--- a/core/java/android/service/quicksettings/TileService.java
+++ b/core/java/android/service/quicksettings/TileService.java
@@ -20,6 +20,7 @@
import android.annotation.SystemApi;
import android.annotation.TestApi;
import android.app.Dialog;
+import android.app.PendingIntent;
import android.app.Service;
import android.app.StatusBarManager;
import android.content.ComponentName;
@@ -336,6 +337,20 @@
}
/**
+ * Starts an {@link android.app.Activity}.
+ * Will collapse Quick Settings after launching.
+ *
+ * @param pendingIntent A PendingIntent for an Activity to be launched immediately.
+ * @hide
+ */
+ public void startActivityAndCollapse(PendingIntent pendingIntent) {
+ try {
+ mService.startActivity(mTileToken, pendingIntent);
+ } catch (RemoteException e) {
+ }
+ }
+
+ /**
* Gets the {@link Tile} for this service.
* <p/>
* This tile may be used to get or set the current state for this
diff --git a/core/java/android/service/wallpaper/IWallpaperEngine.aidl b/core/java/android/service/wallpaper/IWallpaperEngine.aidl
index a41401b..4c51be0 100644
--- a/core/java/android/service/wallpaper/IWallpaperEngine.aidl
+++ b/core/java/android/service/wallpaper/IWallpaperEngine.aidl
@@ -42,7 +42,7 @@
@UnsupportedAppUsage
oneway void destroy();
oneway void setZoomOut(float scale);
- oneway void scalePreview(in Rect positionInWindow);
+ oneway void resizePreview(in Rect positionInWindow);
oneway void removeLocalColorsAreas(in List<RectF> regions);
oneway void addLocalColorsAreas(in List<RectF> regions);
SurfaceControl mirrorSurfaceControl();
diff --git a/core/java/android/service/wallpaper/WallpaperService.java b/core/java/android/service/wallpaper/WallpaperService.java
index 84a233f..de5d395 100644
--- a/core/java/android/service/wallpaper/WallpaperService.java
+++ b/core/java/android/service/wallpaper/WallpaperService.java
@@ -163,7 +163,7 @@
private static final int MSG_TOUCH_EVENT = 10040;
private static final int MSG_REQUEST_WALLPAPER_COLORS = 10050;
private static final int MSG_ZOOM = 10100;
- private static final int MSG_SCALE_PREVIEW = 10110;
+ private static final int MSG_RESIZE_PREVIEW = 10110;
private static final int MSG_REPORT_SHOWN = 10150;
private static final int MSG_UPDATE_DIMMING = 10200;
private static final int MSG_WALLPAPER_FLAGS_CHANGED = 10210;
@@ -322,7 +322,7 @@
@Override
public void setFixedSize(int width, int height) {
- if (!mFixedSizeAllowed) {
+ if (!mFixedSizeAllowed && !mIWallpaperEngine.mIsPreview) {
// Regular apps can't do this. It can only work for
// certain designs of window animations, so you can't
// rely on it.
@@ -1406,16 +1406,9 @@
}
}
- private void scalePreview(Rect position) {
- if (isPreview() && mPreviewSurfacePosition == null && position != null
- || mPreviewSurfacePosition != null
- && !mPreviewSurfacePosition.equals(position)) {
- mPreviewSurfacePosition = position;
- if (mSurfaceControl.isValid()) {
- reposition();
- } else {
- updateSurface(false, false, false);
- }
+ private void resizePreview(Rect position) {
+ if (position != null) {
+ mSurfaceHolder.setFixedSize(position.width(), position.height());
}
}
@@ -2351,8 +2344,8 @@
mCaller.sendMessage(msg);
}
- public void scalePreview(Rect position) {
- Message msg = mCaller.obtainMessageO(MSG_SCALE_PREVIEW, position);
+ public void resizePreview(Rect position) {
+ Message msg = mCaller.obtainMessageO(MSG_RESIZE_PREVIEW, position);
mCaller.sendMessage(msg);
}
@@ -2439,8 +2432,8 @@
case MSG_UPDATE_DIMMING:
mEngine.updateWallpaperDimming(Float.intBitsToFloat(message.arg1));
break;
- case MSG_SCALE_PREVIEW:
- mEngine.scalePreview((Rect) message.obj);
+ case MSG_RESIZE_PREVIEW:
+ mEngine.resizePreview((Rect) message.obj);
break;
case MSG_VISIBILITY_CHANGED:
if (DEBUG) Log.v(TAG, "Visibility change in " + mEngine
diff --git a/core/java/android/speech/RecognitionService.java b/core/java/android/speech/RecognitionService.java
index 6b59f54..1cc772a 100644
--- a/core/java/android/speech/RecognitionService.java
+++ b/core/java/android/speech/RecognitionService.java
@@ -42,6 +42,8 @@
import com.android.internal.util.function.pooled.PooledLambda;
import java.lang.ref.WeakReference;
+import java.util.HashMap;
+import java.util.Map;
import java.util.Objects;
/**
@@ -55,7 +57,7 @@
*/
@SdkConstant(SdkConstantType.SERVICE_ACTION)
public static final String SERVICE_INTERFACE = "android.speech.RecognitionService";
-
+
/**
* Name under which a RecognitionService component publishes information about itself.
* This meta-data should reference an XML resource containing a
@@ -71,17 +73,12 @@
/** Debugging flag */
private static final boolean DBG = false;
+ private static final int DEFAULT_MAX_CONCURRENT_SESSIONS_COUNT = 1;
+
+ private final Map<IBinder, SessionState> mSessions = new HashMap<>();
+
/** Binder of the recognition service */
- private RecognitionServiceBinder mBinder = new RecognitionServiceBinder(this);
-
- /**
- * The current callback of an application that invoked the
- *
- * {@link RecognitionService#onStartListening(Intent, Callback)} method
- */
- private Callback mCurrentCallback = null;
-
- private boolean mStartedDataDelivery;
+ private final RecognitionServiceBinder mBinder = new RecognitionServiceBinder(this);
private static final int MSG_START_LISTENING = 1;
@@ -110,7 +107,7 @@
dispatchCancel((IRecognitionListener) msg.obj);
break;
case MSG_RESET:
- dispatchClearCallback();
+ dispatchClearCallback((IRecognitionListener) msg.obj);
break;
case MSG_CHECK_RECOGNITION_SUPPORT:
Pair<Intent, IRecognitionSupportCallback> intentAndListener =
@@ -127,71 +124,90 @@
private void dispatchStartListening(Intent intent, final IRecognitionListener listener,
@NonNull AttributionSource attributionSource) {
+ Callback currentCallback = null;
+ SessionState sessionState = mSessions.get(listener.asBinder());
+
try {
- if (mCurrentCallback == null) {
- boolean preflightPermissionCheckPassed =
- intent.hasExtra(RecognizerIntent.EXTRA_AUDIO_SOURCE)
- || checkPermissionForPreflightNotHardDenied(attributionSource);
- if (preflightPermissionCheckPassed) {
- if (DBG) {
- Log.d(TAG, "created new mCurrentCallback, listener = "
- + listener.asBinder());
- }
- mCurrentCallback = new Callback(listener, attributionSource);
- RecognitionService.this.onStartListening(intent, mCurrentCallback);
+ if (sessionState == null) {
+ if (mSessions.size() >= getMaxConcurrentSessionsCount()) {
+ listener.onError(SpeechRecognizer.ERROR_RECOGNIZER_BUSY);
+ Log.i(TAG, "#startListening received "
+ + "when the service's capacity is full - ignoring this call.");
+ return;
}
- if (!preflightPermissionCheckPassed || !checkPermissionAndStartDataDelivery()) {
+ boolean preflightPermissionCheckPassed =
+ intent.hasExtra(RecognizerIntent.EXTRA_AUDIO_SOURCE)
+ || checkPermissionForPreflightNotHardDenied(attributionSource);
+ if (preflightPermissionCheckPassed) {
+ currentCallback = new Callback(listener, attributionSource);
+ sessionState = new SessionState(currentCallback);
+ RecognitionService.this.onStartListening(intent, currentCallback);
+ }
+
+ if (!preflightPermissionCheckPassed
+ || !checkPermissionAndStartDataDelivery(sessionState)) {
listener.onError(SpeechRecognizer.ERROR_INSUFFICIENT_PERMISSIONS);
if (preflightPermissionCheckPassed) {
- // If we attempted to start listening, cancel the callback
- RecognitionService.this.onCancel(mCurrentCallback);
- dispatchClearCallback();
+ // If start listening was attempted, cancel the callback.
+ RecognitionService.this.onCancel(currentCallback);
+ finishDataDelivery(sessionState);
+ sessionState.reset();
}
- Log.i(TAG, "caller doesn't have permission:"
- + Manifest.permission.RECORD_AUDIO);
+ Log.i(TAG, "#startListening received from a caller "
+ + "without permission " + Manifest.permission.RECORD_AUDIO + ".");
+ } else {
+ if (DBG) {
+ Log.d(TAG, "Added a new session to the map.");
+ }
+ mSessions.put(listener.asBinder(), sessionState);
}
} else {
- listener.onError(SpeechRecognizer.ERROR_RECOGNIZER_BUSY);
- Log.i(TAG, "concurrent startListening received - ignoring this call");
+ listener.onError(SpeechRecognizer.ERROR_CLIENT);
+ Log.i(TAG, "#startListening received "
+ + "for a listener which is already in session - ignoring this call.");
}
} catch (RemoteException e) {
- Log.d(TAG, "onError call from startListening failed");
+ Log.d(TAG, "#onError call from #startListening failed.");
}
}
private void dispatchStopListening(IRecognitionListener listener) {
- try {
- if (mCurrentCallback == null) {
+ SessionState sessionState = mSessions.get(listener.asBinder());
+ if (sessionState == null) {
+ try {
listener.onError(SpeechRecognizer.ERROR_CLIENT);
- Log.w(TAG, "stopListening called with no preceding startListening - ignoring");
- } else if (mCurrentCallback.mListener.asBinder() != listener.asBinder()) {
- listener.onError(SpeechRecognizer.ERROR_RECOGNIZER_BUSY);
- Log.w(TAG, "stopListening called by other caller than startListening - ignoring");
- } else { // the correct state
- RecognitionService.this.onStopListening(mCurrentCallback);
+ } catch (RemoteException e) {
+ Log.d(TAG, "#onError call from #stopListening failed.");
}
- } catch (RemoteException e) { // occurs if onError fails
- Log.d(TAG, "onError call from stopListening failed");
+ Log.w(TAG, "#stopListening received for a listener "
+ + "which has not started a session - ignoring this call.");
+ } else {
+ RecognitionService.this.onStopListening(sessionState.mCallback);
}
}
private void dispatchCancel(IRecognitionListener listener) {
- if (mCurrentCallback == null) {
- if (DBG) Log.d(TAG, "cancel called with no preceding startListening - ignoring");
- } else if (mCurrentCallback.mListener.asBinder() != listener.asBinder()) {
- Log.w(TAG, "cancel called by client who did not call startListening - ignoring");
- } else { // the correct state
- RecognitionService.this.onCancel(mCurrentCallback);
- dispatchClearCallback();
- if (DBG) Log.d(TAG, "canceling - setting mCurrentCallback to null");
+ SessionState sessionState = mSessions.get(listener.asBinder());
+ if (sessionState == null) {
+ Log.w(TAG, "#cancel received for a listener which has not started a session "
+ + "- ignoring this call.");
+ } else {
+ RecognitionService.this.onCancel(sessionState.mCallback);
+ dispatchClearCallback(listener);
}
}
- private void dispatchClearCallback() {
- finishDataDelivery();
- mCurrentCallback = null;
- mStartedDataDelivery = false;
+ private void dispatchClearCallback(IRecognitionListener listener) {
+ SessionState sessionState = mSessions.remove(listener.asBinder());
+ if (sessionState != null) {
+ if (DBG) {
+ Log.d(TAG, "Removed session from the map for listener = "
+ + listener.asBinder() + ".");
+ }
+ finishDataDelivery(sessionState);
+ sessionState.reset();
+ }
}
private void dispatchCheckRecognitionSupport(
@@ -203,11 +219,11 @@
RecognitionService.this.onTriggerModelDownload(intent);
}
- private class StartListeningArgs {
+ private static class StartListeningArgs {
public final Intent mIntent;
public final IRecognitionListener mListener;
- public final @NonNull AttributionSource mAttributionSource;
+ @NonNull public final AttributionSource mAttributionSource;
public StartListeningArgs(Intent intent, IRecognitionListener listener,
@NonNull AttributionSource attributionSource) {
@@ -306,28 +322,43 @@
}
private void handleAttributionContextCreation(@NonNull AttributionSource attributionSource) {
- if (mCurrentCallback != null
- && mCurrentCallback.mCallingAttributionSource.equals(attributionSource)) {
- mCurrentCallback.mAttributionContextCreated = true;
+ for (SessionState sessionState : mSessions.values()) {
+ Callback currentCallback = sessionState.mCallback;
+ if (currentCallback != null
+ && currentCallback.mCallingAttributionSource.equals(attributionSource)) {
+ currentCallback.mAttributionContextCreated = true;
+ }
}
}
@Override
public final IBinder onBind(final Intent intent) {
- if (DBG) Log.d(TAG, "onBind, intent=" + intent);
+ if (DBG) Log.d(TAG, "#onBind, intent=" + intent);
return mBinder;
}
@Override
public void onDestroy() {
- if (DBG) Log.d(TAG, "onDestroy");
- finishDataDelivery();
- mCurrentCallback = null;
+ if (DBG) Log.d(TAG, "#onDestroy");
+ for (SessionState sessionState : mSessions.values()) {
+ finishDataDelivery(sessionState);
+ sessionState.reset();
+ }
+ mSessions.clear();
mBinder.clearReference();
super.onDestroy();
}
/**
+ * Returns the maximal number of recognition sessions ongoing at the same time.
+ * <p>
+ * The default value is 1, meaning concurrency should be enabled by overriding this method.
+ */
+ public int getMaxConcurrentSessionsCount() {
+ return DEFAULT_MAX_CONCURRENT_SESSIONS_COUNT;
+ }
+
+ /**
* This class receives callbacks from the speech recognition service and forwards them to the
* user. An instance of this class is passed to the
* {@link RecognitionService#onStartListening(Intent, Callback)} method. Recognizers may call
@@ -335,8 +366,8 @@
*/
public class Callback {
private final IRecognitionListener mListener;
- private final @NonNull AttributionSource mCallingAttributionSource;
- private @Nullable Context mAttributionContext;
+ @NonNull private final AttributionSource mCallingAttributionSource;
+ @Nullable private Context mAttributionContext;
private boolean mAttributionContextCreated;
private Callback(IRecognitionListener listener,
@@ -355,7 +386,7 @@
/**
* The service should call this method when sound has been received. The purpose of this
* function is to allow giving feedback to the user regarding the captured audio.
- *
+ *
* @param buffer a buffer containing a sequence of big-endian 16-bit integers representing a
* single channel audio stream. The sample rate is implementation dependent.
*/
@@ -372,11 +403,11 @@
/**
* The service should call this method when a network or recognition error occurred.
- *
+ *
* @param error code is defined in {@link SpeechRecognizer}
*/
public void error(@SpeechRecognizer.RecognitionError int error) throws RemoteException {
- Message.obtain(mHandler, MSG_RESET).sendToTarget();
+ Message.obtain(mHandler, MSG_RESET, mListener).sendToTarget();
mListener.onError(error);
}
@@ -386,7 +417,7 @@
* {@link #results(Bundle)} when partial results are ready. This method may be called zero,
* one or multiple times for each call to {@link SpeechRecognizer#startListening(Intent)},
* depending on the speech recognition service implementation.
- *
+ *
* @param partialResults the returned results. To retrieve the results in
* ArrayList<String> format use {@link Bundle#getStringArrayList(String)} with
* {@link SpeechRecognizer#RESULTS_RECOGNITION} as a parameter
@@ -398,7 +429,7 @@
/**
* The service should call this method when the endpointer is ready for the user to start
* speaking.
- *
+ *
* @param params parameters set by the recognition service. Reserved for future use.
*/
public void readyForSpeech(Bundle params) throws RemoteException {
@@ -407,20 +438,20 @@
/**
* The service should call this method when recognition results are ready.
- *
+ *
* @param results the recognition results. To retrieve the results in {@code
* ArrayList<String>} format use {@link Bundle#getStringArrayList(String)} with
* {@link SpeechRecognizer#RESULTS_RECOGNITION} as a parameter
*/
public void results(Bundle results) throws RemoteException {
- Message.obtain(mHandler, MSG_RESET).sendToTarget();
+ Message.obtain(mHandler, MSG_RESET, mListener).sendToTarget();
mListener.onResults(results);
}
/**
* The service should call this method when the sound level in the audio stream has changed.
* There is no guarantee that this method will be called.
- *
+ *
* @param rmsdB the new RMS dB value
*/
public void rmsChanged(float rmsdB) throws RemoteException {
@@ -444,7 +475,7 @@
*/
@SuppressLint({"CallbackMethodName", "RethrowRemoteException"})
public void endOfSegmentedSession() throws RemoteException {
- Message.obtain(mHandler, MSG_RESET).sendToTarget();
+ Message.obtain(mHandler, MSG_RESET, mListener).sendToTarget();
mListener.onEndOfSegmentedSession();
}
@@ -469,7 +500,8 @@
* AttributionSource)
*/
@SuppressLint("CallbackMethodName")
- public @NonNull AttributionSource getCallingAttributionSource() {
+ @NonNull
+ public AttributionSource getCallingAttributionSource() {
return mCallingAttributionSource;
}
@@ -490,7 +522,6 @@
* these methods on any thread.
*/
public static class SupportCallback {
-
private final IRecognitionSupportCallback mCallback;
private SupportCallback(IRecognitionSupportCallback callback) {
@@ -521,7 +552,7 @@
}
}
-/** Binder of the recognition service */
+ /** Binder of the recognition service. */
private static final class RecognitionServiceBinder extends IRecognitionService.Stub {
private final WeakReference<RecognitionService> mServiceRef;
@@ -538,7 +569,7 @@
final RecognitionService service = mServiceRef.get();
if (service != null) {
service.mHandler.sendMessage(Message.obtain(service.mHandler,
- MSG_START_LISTENING, service.new StartListeningArgs(
+ MSG_START_LISTENING, new StartListeningArgs(
recognizerIntent, listener, attributionSource)));
}
}
@@ -589,17 +620,21 @@
}
}
- private boolean checkPermissionAndStartDataDelivery() {
- if (mCurrentCallback.mAttributionContextCreated) {
+ private boolean checkPermissionAndStartDataDelivery(SessionState sessionState) {
+ if (sessionState.mCallback.mAttributionContextCreated) {
return true;
}
+
if (PermissionChecker.checkPermissionAndStartDataDelivery(
- RecognitionService.this, Manifest.permission.RECORD_AUDIO,
- mCurrentCallback.getAttributionContextForCaller().getAttributionSource(),
- /*message*/ null) == PermissionChecker.PERMISSION_GRANTED) {
- mStartedDataDelivery = true;
+ RecognitionService.this,
+ Manifest.permission.RECORD_AUDIO,
+ sessionState.mCallback.getAttributionContextForCaller().getAttributionSource(),
+ /* message */ null)
+ == PermissionChecker.PERMISSION_GRANTED) {
+ sessionState.mStartedDataDelivery = true;
}
- return mStartedDataDelivery;
+
+ return sessionState.mStartedDataDelivery;
}
private boolean checkPermissionForPreflightNotHardDenied(AttributionSource attributionSource) {
@@ -609,12 +644,39 @@
|| result == PermissionChecker.PERMISSION_SOFT_DENIED;
}
- void finishDataDelivery() {
- if (mStartedDataDelivery) {
- mStartedDataDelivery = false;
+ void finishDataDelivery(SessionState sessionState) {
+ if (sessionState.mStartedDataDelivery) {
+ sessionState.mStartedDataDelivery = false;
final String op = AppOpsManager.permissionToOp(Manifest.permission.RECORD_AUDIO);
PermissionChecker.finishDataDelivery(RecognitionService.this, op,
- mCurrentCallback.getAttributionContextForCaller().getAttributionSource());
+ sessionState.mCallback.getAttributionContextForCaller().getAttributionSource());
+ }
+ }
+
+ /**
+ * Data class containing information about an ongoing session:
+ * <ul>
+ * <li> {@link SessionState#mCallback} - callback of the client that invoked the
+ * {@link RecognitionService#onStartListening(Intent, Callback)} method;
+ * <li> {@link SessionState#mStartedDataDelivery} - flag denoting if data
+ * is being delivered to the client.
+ */
+ private static class SessionState {
+ private Callback mCallback;
+ private boolean mStartedDataDelivery;
+
+ SessionState(Callback callback, boolean startedDataDelivery) {
+ mCallback = callback;
+ mStartedDataDelivery = startedDataDelivery;
+ }
+
+ SessionState(Callback currentCallback) {
+ this(currentCallback, false);
+ }
+
+ void reset() {
+ mCallback = null;
+ mStartedDataDelivery = false;
}
}
}
diff --git a/core/java/android/telephony/TelephonyCallback.java b/core/java/android/telephony/TelephonyCallback.java
index 257f3b7..e90ae75 100644
--- a/core/java/android/telephony/TelephonyCallback.java
+++ b/core/java/android/telephony/TelephonyCallback.java
@@ -582,6 +582,14 @@
@RequiresPermission(Manifest.permission.READ_PRECISE_PHONE_STATE)
public static final int EVENT_LINK_CAPACITY_ESTIMATE_CHANGED = 37;
+ /**
+ * Event to norify the Anbr information from Radio to Ims.
+ *
+ * @see ImsCallSessionImplBase#callSessionNotifyAnbr.
+ *
+ * @hide
+ */
+ public static final int EVENT_TRIGGER_NOTIFY_ANBR = 38;
/**
* @hide
@@ -623,7 +631,8 @@
EVENT_DATA_ENABLED_CHANGED,
EVENT_ALLOWED_NETWORK_TYPE_LIST_CHANGED,
EVENT_LEGACY_CALL_STATE_CHANGED,
- EVENT_LINK_CAPACITY_ESTIMATE_CHANGED
+ EVENT_LINK_CAPACITY_ESTIMATE_CHANGED,
+ EVENT_TRIGGER_NOTIFY_ANBR
})
@Retention(RetentionPolicy.SOURCE)
public @interface TelephonyEvent {
diff --git a/core/java/android/text/FontConfig.java b/core/java/android/text/FontConfig.java
index 32b3bc6..cb488b0 100644
--- a/core/java/android/text/FontConfig.java
+++ b/core/java/android/text/FontConfig.java
@@ -36,6 +36,7 @@
import java.io.File;
import java.lang.annotation.Retention;
import java.util.ArrayList;
+import java.util.Collections;
import java.util.List;
import java.util.Objects;
@@ -55,6 +56,7 @@
public final class FontConfig implements Parcelable {
private final @NonNull List<FontFamily> mFamilies;
private final @NonNull List<Alias> mAliases;
+ private final @NonNull List<NamedFamilyList> mNamedFamilyLists;
private final long mLastModifiedTimeMillis;
private final int mConfigVersion;
@@ -67,14 +69,25 @@
* @hide Only system server can create this instance and passed via IPC.
*/
public FontConfig(@NonNull List<FontFamily> families, @NonNull List<Alias> aliases,
+ @NonNull List<NamedFamilyList> namedFamilyLists,
long lastModifiedTimeMillis, @IntRange(from = 0) int configVersion) {
mFamilies = families;
mAliases = aliases;
+ mNamedFamilyLists = namedFamilyLists;
mLastModifiedTimeMillis = lastModifiedTimeMillis;
mConfigVersion = configVersion;
}
/**
+ * @hide Keep this constructor for reoborectric.
+ */
+ public FontConfig(@NonNull List<FontFamily> families, @NonNull List<Alias> aliases,
+ long lastModifiedTimeMillis, @IntRange(from = 0) int configVersion) {
+ this(families, aliases, Collections.emptyList(), lastModifiedTimeMillis, configVersion);
+ }
+
+
+ /**
* Returns the ordered list of font families available in the system.
*
* @return a list of font families.
@@ -94,6 +107,10 @@
return mAliases;
}
+ public @NonNull List<NamedFamilyList> getNamedFamilyLists() {
+ return mNamedFamilyLists;
+ }
+
/**
* Returns the last modified time in milliseconds.
*
@@ -133,8 +150,9 @@
@Override
public void writeToParcel(@NonNull Parcel dest, int flags) {
- dest.writeParcelableList(mFamilies, flags);
- dest.writeParcelableList(mAliases, flags);
+ dest.writeTypedList(mFamilies, flags);
+ dest.writeTypedList(mAliases, flags);
+ dest.writeTypedList(mNamedFamilyLists, flags);
dest.writeLong(mLastModifiedTimeMillis);
dest.writeInt(mConfigVersion);
}
@@ -142,13 +160,15 @@
public static final @NonNull Creator<FontConfig> CREATOR = new Creator<FontConfig>() {
@Override
public FontConfig createFromParcel(Parcel source) {
- List<FontFamily> families = source.readParcelableList(new ArrayList<>(),
- FontFamily.class.getClassLoader(), android.text.FontConfig.FontFamily.class);
- List<Alias> aliases = source.readParcelableList(new ArrayList<>(),
- Alias.class.getClassLoader(), android.text.FontConfig.Alias.class);
+ final List<FontFamily> families = new ArrayList<>();
+ source.readTypedList(families, FontFamily.CREATOR);
+ final List<Alias> aliases = new ArrayList<>();
+ source.readTypedList(aliases, Alias.CREATOR);
+ final List<NamedFamilyList> familyLists = new ArrayList<>();
+ source.readTypedList(familyLists, NamedFamilyList.CREATOR);
long lastModifiedDate = source.readLong();
int configVersion = source.readInt();
- return new FontConfig(families, aliases, lastModifiedDate, configVersion);
+ return new FontConfig(families, aliases, familyLists, lastModifiedDate, configVersion);
}
@Override
@@ -506,7 +526,6 @@
*/
public static final class FontFamily implements Parcelable {
private final @NonNull List<Font> mFonts;
- private final @Nullable String mName;
private final @NonNull LocaleList mLocaleList;
private final @Variant int mVariant;
@@ -547,10 +566,9 @@
*
* @hide Only system server can create this instance and passed via IPC.
*/
- public FontFamily(@NonNull List<Font> fonts, @Nullable String name,
- @NonNull LocaleList localeList, @Variant int variant) {
+ public FontFamily(@NonNull List<Font> fonts, @NonNull LocaleList localeList,
+ @Variant int variant) {
mFonts = fonts;
- mName = name;
mLocaleList = localeList;
mVariant = variant;
}
@@ -577,9 +595,13 @@
*
* When the name of a {@link FontFamily} is null, it will be appended to all of the
* {@code Fallback List}s.
+ *
+ * @deprecated From API 34, this function always returns null. All font families which have
+ * name attribute will be reported as a {@link NamedFamilyList}.
*/
+ @Deprecated
public @Nullable String getName() {
- return mName;
+ return null;
}
/**
@@ -606,8 +628,7 @@
@Override
public void writeToParcel(@NonNull Parcel dest, int flags) {
- dest.writeParcelableList(mFonts, flags);
- dest.writeString8(mName);
+ dest.writeTypedList(mFonts, flags);
dest.writeString8(mLocaleList.toLanguageTags());
dest.writeInt(mVariant);
}
@@ -616,13 +637,12 @@
@Override
public FontFamily createFromParcel(Parcel source) {
- List<Font> fonts = source.readParcelableList(
- new ArrayList<>(), Font.class.getClassLoader(), android.text.FontConfig.Font.class);
- String name = source.readString8();
+ List<Font> fonts = new ArrayList<>();
+ source.readTypedList(fonts, Font.CREATOR);
String langTags = source.readString8();
int variant = source.readInt();
- return new FontFamily(fonts, name, LocaleList.forLanguageTags(langTags), variant);
+ return new FontFamily(fonts, LocaleList.forLanguageTags(langTags), variant);
}
@Override
@@ -659,23 +679,118 @@
FontFamily that = (FontFamily) o;
return mVariant == that.mVariant
&& Objects.equals(mFonts, that.mFonts)
- && Objects.equals(mName, that.mName)
&& Objects.equals(mLocaleList, that.mLocaleList);
}
@Override
public int hashCode() {
- return Objects.hash(mFonts, mName, mLocaleList, mVariant);
+ return Objects.hash(mFonts, mLocaleList, mVariant);
}
@Override
public String toString() {
return "FontFamily{"
+ "mFonts=" + mFonts
- + ", mName='" + mName + '\''
+ ", mLocaleList=" + mLocaleList
+ ", mVariant=" + mVariant
+ '}';
}
}
+
+ /**
+ * Represents list of font family in the system font configuration.
+ *
+ * In the fonts_customization.xml, it can define the list of FontFamily as a named family. The
+ * list of FontFamily is treated as a fallback list when drawing.
+ *
+ * @see android.graphics.fonts.FontFamily
+ */
+ public static final class NamedFamilyList implements Parcelable {
+ private final List<FontFamily> mFamilies;
+ private final String mName;
+
+ /** @hide */
+ public NamedFamilyList(@NonNull List<FontFamily> families, @NonNull String name) {
+ mFamilies = families;
+ mName = name;
+ }
+
+ /** @hide */
+ public NamedFamilyList(@NonNull FontFamily family) {
+ mFamilies = new ArrayList<>();
+ mFamilies.add(family);
+ mName = family.getName();
+ }
+
+ /**
+ * A list of font families.
+ *
+ * @return a list of font families.
+ */
+ public @NonNull List<FontFamily> getFamilies() {
+ return mFamilies;
+ }
+
+ /**
+ * Returns the name of the {@link FontFamily}.
+ *
+ * This name is used to create a new {@code Fallback List}.
+ *
+ * For example, if the {@link FontFamily} has the name "serif", then the system will create
+ * a “serif” {@code Fallback List} and it can be used by creating a Typeface via
+ * {@code Typeface.create("serif", Typeface.NORMAL);}
+ */
+ public @NonNull String getName() {
+ return mName;
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(@androidx.annotation.NonNull Parcel dest, int flags) {
+ dest.writeTypedList(mFamilies, flags);
+ dest.writeString8(mName);
+ }
+
+ public static final @NonNull Creator<NamedFamilyList> CREATOR = new Creator<>() {
+
+ @Override
+ public NamedFamilyList createFromParcel(Parcel source) {
+ final List<FontFamily> families = new ArrayList<>();
+ source.readTypedList(families, FontFamily.CREATOR);
+ String name = source.readString8();
+ return new NamedFamilyList(families, name);
+ }
+
+ @Override
+ public NamedFamilyList[] newArray(int size) {
+ return new NamedFamilyList[size];
+ }
+ };
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ NamedFamilyList that = (NamedFamilyList) o;
+ return Objects.equals(mFamilies, that.mFamilies) && Objects.equals(mName,
+ that.mName);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(mFamilies, mName);
+ }
+
+ @Override
+ public String toString() {
+ return "NamedFamilyList{"
+ + "mFamilies=" + mFamilies
+ + ", mName='" + mName + '\''
+ + '}';
+ }
+ }
}
diff --git a/core/java/android/view/AccessibilityInteractionController.java b/core/java/android/view/AccessibilityInteractionController.java
index 44168ca..f299238 100644
--- a/core/java/android/view/AccessibilityInteractionController.java
+++ b/core/java/android/view/AccessibilityInteractionController.java
@@ -21,6 +21,8 @@
import static android.view.accessibility.AccessibilityNodeInfo.EXTRA_DATA_REQUESTED_KEY;
import static android.view.accessibility.AccessibilityNodeInfo.EXTRA_DATA_TEXT_CHARACTER_LOCATION_KEY;
+import static com.android.internal.util.function.pooled.PooledLambda.obtainMessage;
+
import android.accessibilityservice.AccessibilityService;
import android.annotation.NonNull;
import android.graphics.Matrix;
@@ -1961,4 +1963,23 @@
}
}
}
+
+ /** Attaches an accessibility overlay to the specified window. */
+ public void attachAccessibilityOverlayToWindowClientThread(SurfaceControl sc) {
+ mHandler.sendMessage(
+ obtainMessage(
+ AccessibilityInteractionController
+ ::attachAccessibilityOverlayToWindowUiThread,
+ this,
+ sc));
+ }
+
+ private void attachAccessibilityOverlayToWindowUiThread(SurfaceControl sc) {
+ SurfaceControl parent = mViewRootImpl.getSurfaceControl();
+ if (parent.isValid()) {
+ SurfaceControl.Transaction t = new SurfaceControl.Transaction();
+ t.reparent(sc, parent).apply();
+ t.close();
+ }
+ }
}
diff --git a/core/java/android/view/DisplayCutout.java b/core/java/android/view/DisplayCutout.java
index 83a7b3f..712d1d6 100644
--- a/core/java/android/view/DisplayCutout.java
+++ b/core/java/android/view/DisplayCutout.java
@@ -23,6 +23,7 @@
import static android.view.DisplayCutoutProto.BOUND_RIGHT;
import static android.view.DisplayCutoutProto.BOUND_TOP;
import static android.view.DisplayCutoutProto.INSETS;
+import static android.view.DisplayCutoutProto.WATERFALL_INSETS;
import static android.view.Surface.ROTATION_0;
import static com.android.internal.annotations.VisibleForTesting.Visibility.PRIVATE;
@@ -830,7 +831,7 @@
mBounds.getRect(BOUNDS_POSITION_TOP).dumpDebug(proto, BOUND_TOP);
mBounds.getRect(BOUNDS_POSITION_RIGHT).dumpDebug(proto, BOUND_RIGHT);
mBounds.getRect(BOUNDS_POSITION_BOTTOM).dumpDebug(proto, BOUND_BOTTOM);
- mWaterfallInsets.toRect().dumpDebug(proto, INSETS);
+ mWaterfallInsets.toRect().dumpDebug(proto, WATERFALL_INSETS);
proto.end(token);
}
diff --git a/core/java/android/view/DisplayInfo.java b/core/java/android/view/DisplayInfo.java
index 85cb517..c2b6bc5 100644
--- a/core/java/android/view/DisplayInfo.java
+++ b/core/java/android/view/DisplayInfo.java
@@ -18,6 +18,7 @@
import static android.view.DisplayInfoProto.APP_HEIGHT;
import static android.view.DisplayInfoProto.APP_WIDTH;
+import static android.view.DisplayInfoProto.CUTOUT;
import static android.view.DisplayInfoProto.FLAGS;
import static android.view.DisplayInfoProto.LOGICAL_HEIGHT;
import static android.view.DisplayInfoProto.LOGICAL_WIDTH;
@@ -861,6 +862,9 @@
protoOutputStream.write(APP_HEIGHT, appHeight);
protoOutputStream.write(NAME, name);
protoOutputStream.write(FLAGS, flags);
+ if (displayCutout != null) {
+ displayCutout.dumpDebug(protoOutputStream, CUTOUT);
+ }
protoOutputStream.end(token);
}
diff --git a/core/java/android/view/IWindowSession.aidl b/core/java/android/view/IWindowSession.aidl
index 03ccb47..e4d878a 100644
--- a/core/java/android/view/IWindowSession.aidl
+++ b/core/java/android/view/IWindowSession.aidl
@@ -299,7 +299,8 @@
*/
void grantInputChannel(int displayId, in SurfaceControl surface, in IWindow window,
in IBinder hostInputToken, int flags, int privateFlags, int type,
- in IBinder focusGrantToken, String inputHandleName, out InputChannel outInputChannel);
+ in IBinder windowToken, in IBinder focusGrantToken, String inputHandleName,
+ out InputChannel outInputChannel);
/**
* Update the flags on an input channel associated with a particular surface.
diff --git a/core/java/android/view/SurfaceControlViewHost.java b/core/java/android/view/SurfaceControlViewHost.java
index 0a134be..50ce7b6 100644
--- a/core/java/android/view/SurfaceControlViewHost.java
+++ b/core/java/android/view/SurfaceControlViewHost.java
@@ -376,6 +376,7 @@
public void setView(@NonNull View view, @NonNull WindowManager.LayoutParams attrs) {
Objects.requireNonNull(view);
attrs.flags |= WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED;
+ addWindowToken(attrs);
view.setLayoutParams(attrs);
mViewRoot.setView(view, attrs, null);
}
@@ -453,4 +454,11 @@
public IBinder getFocusGrantToken() {
return mWm.getFocusGrantToken();
}
+
+ private void addWindowToken(WindowManager.LayoutParams attrs) {
+ final WindowManagerImpl wm =
+ (WindowManagerImpl) mViewRoot.mContext.getSystemService(Context.WINDOW_SERVICE);
+ attrs.token = wm.getDefaultToken();
+ }
}
+
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index b2973ef..ff4ea81 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -10810,6 +10810,15 @@
}
}
}
+
+ public void attachAccessibilityOverlayToWindow(SurfaceControl sc) {
+ ViewRootImpl viewRootImpl = mViewRootImpl.get();
+ if (viewRootImpl != null) {
+ viewRootImpl
+ .getAccessibilityInteractionController()
+ .attachAccessibilityOverlayToWindowClientThread(sc);
+ }
+ }
}
/**
diff --git a/core/java/android/view/WindowManagerImpl.java b/core/java/android/view/WindowManagerImpl.java
index 2228b9f..19d49d9 100644
--- a/core/java/android/view/WindowManagerImpl.java
+++ b/core/java/android/view/WindowManagerImpl.java
@@ -408,4 +408,8 @@
}
return null;
}
+
+ IBinder getDefaultToken() {
+ return mDefaultToken;
+ }
}
diff --git a/core/java/android/view/WindowlessWindowManager.java b/core/java/android/view/WindowlessWindowManager.java
index 69340aa..a9fea017 100644
--- a/core/java/android/view/WindowlessWindowManager.java
+++ b/core/java/android/view/WindowlessWindowManager.java
@@ -166,11 +166,12 @@
if (mRealWm instanceof IWindowSession.Stub) {
mRealWm.grantInputChannel(displayId,
new SurfaceControl(sc, "WindowlessWindowManager.addToDisplay"),
- window, mHostInputToken, attrs.flags, attrs.privateFlags, attrs.type,
+ window, mHostInputToken,
+ attrs.flags, attrs.privateFlags, attrs.type, attrs.token,
mFocusGrantToken, attrs.getTitle().toString(), outInputChannel);
} else {
mRealWm.grantInputChannel(displayId, sc, window, mHostInputToken, attrs.flags,
- attrs.privateFlags, attrs.type, mFocusGrantToken,
+ attrs.privateFlags, attrs.type, attrs.token, mFocusGrantToken,
attrs.getTitle().toString(), outInputChannel);
}
} catch (RemoteException e) {
@@ -508,8 +509,9 @@
@Override
public void grantInputChannel(int displayId, SurfaceControl surface, IWindow window,
- IBinder hostInputToken, int flags, int privateFlags, int type, IBinder focusGrantToken,
- String inputHandleName, InputChannel outInputChannel) {
+ IBinder hostInputToken, int flags, int privateFlags, int type,
+ IBinder windowToken, IBinder focusGrantToken, String inputHandleName,
+ InputChannel outInputChannel) {
}
@Override
diff --git a/core/java/android/view/accessibility/AccessibilityInteractionClient.java b/core/java/android/view/accessibility/AccessibilityInteractionClient.java
index 06a6de9..52eda0a 100644
--- a/core/java/android/view/accessibility/AccessibilityInteractionClient.java
+++ b/core/java/android/view/accessibility/AccessibilityInteractionClient.java
@@ -46,6 +46,7 @@
import android.util.SparseArray;
import android.util.SparseLongArray;
import android.view.Display;
+import android.view.SurfaceControl;
import android.view.ViewConfiguration;
import android.window.ScreenCapture;
@@ -1626,4 +1627,21 @@
new HashSet<String>(Arrays.asList("getStackTrace", "logTraceClient")),
FLAGS_ACCESSIBILITY_INTERACTION_CLIENT);
}
+
+ /** Attaches an accessibility overlay to the specified window. */
+ public void attachAccessibilityOverlayToWindow(
+ int connectionId, int accessibilityWindowId, SurfaceControl sc) {
+ synchronized (mInstanceLock) {
+ try {
+ IAccessibilityServiceConnection connection = getConnection(connectionId);
+ if (connection == null) {
+ Log.e(LOG_TAG, "Error while getting service connection.");
+ return;
+ }
+ connection.attachAccessibilityOverlayToWindow(accessibilityWindowId, sc);
+ } catch (RemoteException re) {
+ Log.e(LOG_TAG, "Error while calling remote attachAccessibilityOverlayToWindow", re);
+ }
+ }
+ }
}
diff --git a/core/java/android/view/accessibility/IAccessibilityInteractionConnection.aidl b/core/java/android/view/accessibility/IAccessibilityInteractionConnection.aidl
index fb01921..0eeba7c 100644
--- a/core/java/android/view/accessibility/IAccessibilityInteractionConnection.aidl
+++ b/core/java/android/view/accessibility/IAccessibilityInteractionConnection.aidl
@@ -20,6 +20,7 @@
import android.graphics.Point;
import android.os.Bundle;
import android.view.MagnificationSpec;
+import android.view.SurfaceControl;
import android.view.accessibility.AccessibilityNodeInfo;
import android.view.accessibility.IAccessibilityInteractionConnectionCallback;
import android.window.ScreenCapture;
@@ -65,4 +66,5 @@
void takeScreenshotOfWindow(int interactionId,
in ScreenCapture.ScreenCaptureListener listener,
IAccessibilityInteractionConnectionCallback callback);
+ void attachAccessibilityOverlayToWindow(in SurfaceControl sc);
}
diff --git a/core/java/android/view/inputmethod/ImeTracker.java b/core/java/android/view/inputmethod/ImeTracker.java
index f4ecdff..927d769 100644
--- a/core/java/android/view/inputmethod/ImeTracker.java
+++ b/core/java/android/view/inputmethod/ImeTracker.java
@@ -423,6 +423,12 @@
mTag = tag;
}
+ /** Returns the {@link Token#mTag} */
+ @NonNull
+ public String getTag() {
+ return mTag;
+ }
+
/** For Parcelable, no special marshalled objects. */
@Override
public int describeContents() {
diff --git a/core/java/android/view/inputmethod/InputMethodInfo.java b/core/java/android/view/inputmethod/InputMethodInfo.java
index d7c1846..b7da732 100644
--- a/core/java/android/view/inputmethod/InputMethodInfo.java
+++ b/core/java/android/view/inputmethod/InputMethodInfo.java
@@ -34,6 +34,7 @@
import android.content.res.TypedArray;
import android.content.res.XmlResourceParser;
import android.graphics.drawable.Drawable;
+import android.icu.util.ULocale;
import android.inputmethodservice.InputMethodService;
import android.os.Parcel;
import android.os.Parcelable;
@@ -266,11 +267,18 @@
}
final TypedArray a = res.obtainAttributes(
attrs, com.android.internal.R.styleable.InputMethod_Subtype);
+ String pkLanguageTag = a.getString(com.android.internal.R.styleable
+ .InputMethod_Subtype_physicalKeyboardHintLanguageTag);
+ String pkLayoutType = a.getString(com.android.internal.R.styleable
+ .InputMethod_Subtype_physicalKeyboardHintLayoutType);
final InputMethodSubtype subtype = new InputMethodSubtypeBuilder()
.setSubtypeNameResId(a.getResourceId(com.android.internal.R.styleable
.InputMethod_Subtype_label, 0))
.setSubtypeIconResId(a.getResourceId(com.android.internal.R.styleable
.InputMethod_Subtype_icon, 0))
+ .setPhysicalKeyboardHint(
+ pkLanguageTag == null ? null : new ULocale(pkLanguageTag),
+ pkLayoutType == null ? "" : pkLayoutType)
.setLanguageTag(a.getString(com.android.internal.R.styleable
.InputMethod_Subtype_languageTag))
.setSubtypeLocale(a.getString(com.android.internal.R.styleable
diff --git a/core/java/android/view/inputmethod/InputMethodSubtype.java b/core/java/android/view/inputmethod/InputMethodSubtype.java
index 6a02198..e73e7a3 100644
--- a/core/java/android/view/inputmethod/InputMethodSubtype.java
+++ b/core/java/android/view/inputmethod/InputMethodSubtype.java
@@ -39,6 +39,7 @@
import java.util.IllegalFormatException;
import java.util.List;
import java.util.Locale;
+import java.util.Objects;
/**
* This class is used to specify meta information of a subtype contained in an input method editor
@@ -87,6 +88,8 @@
private final int mSubtypeIconResId;
private final int mSubtypeNameResId;
private final CharSequence mSubtypeNameOverride;
+ private final String mPkLanguageTag;
+ private final String mPkLayoutType;
private final int mSubtypeId;
private final String mSubtypeLocale;
private final String mSubtypeLanguageTag;
@@ -190,6 +193,30 @@
private CharSequence mSubtypeNameOverride = "";
/**
+ * Sets the physical keyboard hint information, such as language and layout.
+ *
+ * The system can use the hint information to automatically configure the physical keyboard
+ * for the subtype.
+ *
+ * @param languageTag is the preferred physical keyboard BCP-47 language tag. This is used
+ * to match the keyboardLocale attribute in the physical keyboard definition. If it's
+ * {@code null}, the subtype's language tag will be used.
+ * @param layoutType is the preferred physical keyboard layout, which is used to match the
+ * keyboardLayoutType attribute in the physical keyboard definition. See
+ * {@link android.hardware.input.InputManager#ACTION_QUERY_KEYBOARD_LAYOUTS}.
+ */
+ @NonNull
+ public InputMethodSubtypeBuilder setPhysicalKeyboardHint(@Nullable ULocale languageTag,
+ @NonNull String layoutType) {
+ Objects.requireNonNull(layoutType, "layoutType cannot be null");
+ mPkLanguageTag = languageTag == null ? "" : languageTag.toLanguageTag();
+ mPkLayoutType = layoutType;
+ return this;
+ }
+ private String mPkLanguageTag = "";
+ private String mPkLayoutType = "";
+
+ /**
* @param subtypeId is the unique ID for this subtype. The input method framework keeps
* track of enabled subtypes by ID. When the IME package gets upgraded, enabled IDs will
* stay enabled even if other attributes are different. If the ID is unspecified or 0,
@@ -322,6 +349,8 @@
private InputMethodSubtype(InputMethodSubtypeBuilder builder) {
mSubtypeNameResId = builder.mSubtypeNameResId;
mSubtypeNameOverride = builder.mSubtypeNameOverride;
+ mPkLanguageTag = builder.mPkLanguageTag;
+ mPkLayoutType = builder.mPkLayoutType;
mSubtypeIconResId = builder.mSubtypeIconResId;
mSubtypeLocale = builder.mSubtypeLocale;
mSubtypeLanguageTag = builder.mSubtypeLanguageTag;
@@ -346,6 +375,10 @@
mSubtypeNameResId = source.readInt();
CharSequence cs = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(source);
mSubtypeNameOverride = cs != null ? cs : "";
+ s = source.readString8();
+ mPkLanguageTag = s != null ? s : "";
+ s = source.readString8();
+ mPkLayoutType = s != null ? s : "";
mSubtypeIconResId = source.readInt();
s = source.readString();
mSubtypeLocale = s != null ? s : "";
@@ -378,6 +411,28 @@
}
/**
+ * Returns the physical keyboard BCP-47 language tag.
+ *
+ * @attr ref android.R.styleable#InputMethod_Subtype_physicalKeyboardHintLanguageTag
+ * @see InputMethodSubtypeBuilder#setPhysicalKeyboardHint
+ */
+ @Nullable
+ public ULocale getPhysicalKeyboardHintLanguageTag() {
+ return TextUtils.isEmpty(mPkLanguageTag) ? null : ULocale.forLanguageTag(mPkLanguageTag);
+ }
+
+ /**
+ * Returns the physical keyboard layout type string.
+ *
+ * @attr ref android.R.styleable#InputMethod_Subtype_physicalKeyboardHintLayoutType
+ * @see InputMethodSubtypeBuilder#setPhysicalKeyboardHint
+ */
+ @NonNull
+ public String getPhysicalKeyboardHintLayoutType() {
+ return mPkLayoutType;
+ }
+
+ /**
* @return Resource ID of the subtype icon drawable.
*/
public int getIconResId() {
@@ -729,6 +784,8 @@
public void writeToParcel(Parcel dest, int parcelableFlags) {
dest.writeInt(mSubtypeNameResId);
TextUtils.writeToParcel(mSubtypeNameOverride, dest, parcelableFlags);
+ dest.writeString8(mPkLanguageTag);
+ dest.writeString8(mPkLayoutType);
dest.writeInt(mSubtypeIconResId);
dest.writeString(mSubtypeLocale);
dest.writeString(mSubtypeLanguageTag);
diff --git a/core/proto/android/view/displaycutout.proto b/core/proto/android/view/displaycutout.proto
index ff98e99..72d5303 100644
--- a/core/proto/android/view/displaycutout.proto
+++ b/core/proto/android/view/displaycutout.proto
@@ -31,4 +31,5 @@
optional .android.graphics.RectProto bound_top = 4;
optional .android.graphics.RectProto bound_right = 5;
optional .android.graphics.RectProto bound_bottom = 6;
+ optional .android.graphics.RectProto waterfall_insets = 7;
}
diff --git a/core/proto/android/view/displayinfo.proto b/core/proto/android/view/displayinfo.proto
index 984be2a..f936ed1 100644
--- a/core/proto/android/view/displayinfo.proto
+++ b/core/proto/android/view/displayinfo.proto
@@ -18,6 +18,7 @@
package android.view;
import "frameworks/base/core/proto/android/privacy.proto";
+import "frameworks/base/core/proto/android/view/displaycutout.proto";
option java_multiple_files = true;
@@ -34,4 +35,5 @@
// Eg: "Built-in Screen"
optional string name = 5;
optional int32 flags = 6;
+ optional DisplayCutoutProto cutout = 7;
}
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index bc6df98..78a6a66 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -315,6 +315,7 @@
<protected-broadcast android:name="android.media.MASTER_BALANCE_CHANGED_ACTION" />
<protected-broadcast android:name="android.media.SCO_AUDIO_STATE_CHANGED" />
<protected-broadcast android:name="android.media.ACTION_SCO_AUDIO_STATE_UPDATED" />
+ <protected-broadcast android:name="com.android.server.audio.action.CHECK_MUSIC_ACTIVE" />
<protected-broadcast android:name="android.intent.action.MEDIA_REMOVED" />
<protected-broadcast android:name="android.intent.action.MEDIA_UNMOUNTED" />
@@ -3422,6 +3423,8 @@
<!-- Allows an app to prevent non-system-overlay windows from being drawn on top of it -->
<permission android:name="android.permission.HIDE_OVERLAY_WINDOWS"
+ android:label="@string/permlab_hideOverlayWindows"
+ android:description="@string/permdesc_hideOverlayWindows"
android:protectionLevel="normal" />
<!-- ================================== -->
@@ -3624,6 +3627,11 @@
<permission android:name="android.permission.READ_APP_SPECIFIC_LOCALES"
android:protectionLevel="signature|installer" />
+ <!-- @hide Allows applications to set an application-specific {@link LocaleConfig}.
+ <p>Not for use by third-party applications. -->
+ <permission android:name="android.permission.SET_APP_SPECIFIC_LOCALECONFIG"
+ android:protectionLevel="signature" />
+
<!-- @hide Allows an application to monitor {@link android.provider.Settings.Config} access.
<p>Not for use by third-party applications. -->
<permission android:name="android.permission.MONITOR_DEVICE_CONFIG_ACCESS"
@@ -6351,7 +6359,7 @@
<!-- @SystemApi Allows to access all app shortcuts.
@hide -->
<permission android:name="android.permission.ACCESS_SHORTCUTS"
- android:protectionLevel="signature|role" />
+ android:protectionLevel="signature|role|recents" />
<!-- @SystemApi Allows unlimited calls to shortcut mutation APIs.
@hide -->
diff --git a/core/res/res/values-af/strings.xml b/core/res/res/values-af/strings.xml
index f0cee4d..d3692b8 100644
--- a/core/res/res/values-af/strings.xml
+++ b/core/res/res/values-af/strings.xml
@@ -2352,4 +2352,8 @@
<string name="permdesc_deliverCompanionMessages" msgid="2170847384281412850">"Laat ’n metgeselapp toe om metgeselboodskappe aan ander toestelle af te lewer."</string>
<string name="permlab_startForegroundServicesFromBackground" msgid="6363004936218638382">"Begin voorgronddienste van agtergrond af"</string>
<string name="permdesc_startForegroundServicesFromBackground" msgid="4071826571656001537">"Laat ’n metgeselapp toe om voorgronddienste van agtergrond af te begin"</string>
+ <!-- no translation found for mic_access_on_toast (2666925317663845156) -->
+ <skip />
+ <!-- no translation found for mic_access_off_toast (8111040892954242437) -->
+ <skip />
</resources>
diff --git a/core/res/res/values-am/strings.xml b/core/res/res/values-am/strings.xml
index d963e4f..341e623 100644
--- a/core/res/res/values-am/strings.xml
+++ b/core/res/res/values-am/strings.xml
@@ -392,14 +392,10 @@
<string name="permdesc_runInBackground" msgid="4344539472115495141">"ይህ መተግበሪያ በጀርባ ላይ ማሄድ ይችላል። ይሄ ባትሪውን በይበልጥ ሊጨርሰው ይችላል።"</string>
<string name="permlab_useDataInBackground" msgid="783415807623038947">"በጀርባ ላይ ውሂብ ይጠቀማል"</string>
<string name="permdesc_useDataInBackground" msgid="1230753883865891987">"ይህ መተግበሪያ በጀርባ ላይ ውሂብ ሊጠቀም ይችላል። ይሄ የውሂብ ፍጆታን ሊጨምር ይችላል።"</string>
- <!-- no translation found for permlab_schedule_exact_alarm (6683283918033029730) -->
- <skip />
- <!-- no translation found for permdesc_schedule_exact_alarm (8198009212013211497) -->
- <skip />
- <!-- no translation found for permlab_use_exact_alarm (348045139777131552) -->
- <skip />
- <!-- no translation found for permdesc_use_exact_alarm (7033761461886938912) -->
- <skip />
+ <string name="permlab_schedule_exact_alarm" msgid="6683283918033029730">"በትክክል በጊዜ የተያዙ እርምጃዎችን መርሐግብር ያስይዙ"</string>
+ <string name="permdesc_schedule_exact_alarm" msgid="8198009212013211497">"ይህ መተግበሪያ ሥራ ወደፊት በሚፈለገው ጊዜ እንዲከናወን መርሐግብር ማስያዝ ይችላል። እንዲሁም ይህ ማለት እርስዎ መሣሪያውን በንቃት በማይጠቀሙበት ጊዜ መተግበሪያው ሊያሄድ ይችላል ማለት ነው።"</string>
+ <string name="permlab_use_exact_alarm" msgid="348045139777131552">"ማንቂያዎችን ወይም የክስተት አስታዋሾችን መርሐግብር ያስይዙ"</string>
+ <string name="permdesc_use_exact_alarm" msgid="7033761461886938912">"ይህ መተግበሪያ ወደፊት በሚፈለግበት ጊዜ እርስዎን ለማሳወቅ እንደ ማንቂያዎች እና አስታዋሾች ያሉ እርምጃዎችን መርሐግብር ማስያዝ ይችላል።"</string>
<string name="permlab_persistentActivity" msgid="464970041740567970">"ትግበራ ሁልጊዜ አሂድ ላይ አድርግ"</string>
<string name="permdesc_persistentActivity" product="tablet" msgid="6055271149187369916">"መተግበሪያው የራሱን ክፍሎች በማህደረ ትውስታ ውስጥ በቋሚነት የሚቀጥሉ እንዲያደርግ ይፈቅድለታል። ይህ ለሌላ መተግበሪያዎች ያለውን ማህደረ ትውስታ በመገደብ ጡባዊ ተኮውን ሊያንቀራፍፈው ይችላል።"</string>
<string name="permdesc_persistentActivity" product="tv" msgid="6800526387664131321">"መተግበሪያው የራሱን ክፍሎች በማህደረ ትውስታ ውስጥ በቋሚነት የሚቀጥሉ እንዲያደርግ ይፈቅድለታል። ይህ ለሌላ መተግበሪያዎች ያለውን ማህደረ ትውስታ በመገደብ የእርስዎን Android TV ሊያንቀራፍፈው ይችላል።"</string>
@@ -428,10 +424,8 @@
<string name="permdesc_foregroundServiceRemoteMessaging" msgid="8767598075877576277">"መተግበሪያው የፊት አገልግሎትን በ«remoteMessaging» ዓይነት እንዲጠቀም ይፈቅዳል"</string>
<string name="permlab_foregroundServiceSystemExempted" msgid="1597663713590612685">"የፊት አገልግሎትን በ«systemExempted» ዓይነት ማስሄድ"</string>
<string name="permdesc_foregroundServiceSystemExempted" msgid="947381760834649622">"መተግበሪያው የፊት አገልግሎትን በ«systemExempted» ዓይነት እንዲጠቀም ይፈቅዳል"</string>
- <!-- no translation found for permlab_foregroundServiceFileManagement (2585000987966045030) -->
- <skip />
- <!-- no translation found for permdesc_foregroundServiceFileManagement (417103601269698508) -->
- <skip />
+ <string name="permlab_foregroundServiceFileManagement" msgid="2585000987966045030">"የፊት አገልግሎትን በ«fileManagement» ዓይነት ማስሄድ"</string>
+ <string name="permdesc_foregroundServiceFileManagement" msgid="417103601269698508">"መተግበሪያው የፊት አገልግሎቶችን በ«fileManagement» ዓይነት እንዲጠቀም ያስችላል"</string>
<string name="permlab_foregroundServiceSpecialUse" msgid="7973536745876645082">"የፊት አገልግሎትን በ«specialUse» ዓይነት ማስሄድ"</string>
<string name="permdesc_foregroundServiceSpecialUse" msgid="646713654541885919">"መተግበሪያው የፊት አገልግሎትን በ«specialUse» ዓይነት እንዲጠቀም ይፈቅዳል"</string>
<string name="permlab_getPackageSize" msgid="375391550792886641">"የመተግበሪያ ማከማቻ ቦታ ለካ"</string>
@@ -2350,20 +2344,16 @@
<string name="vdm_pip_blocked" msgid="4036107522497281397">"በዥረት በመልቀቅ ወቅት በሥዕል-ላይ-ሥዕል ማየት አይችሉም"</string>
<string name="system_locale_title" msgid="711882686834677268">"የሥርዓት ነባሪ"</string>
<string name="default_card_name" msgid="9198284935962911468">"ካርድ <xliff:g id="CARDNUMBER">%d</xliff:g>"</string>
- <!-- no translation found for permlab_companionProfileWatch (2457738382085872542) -->
+ <string name="permlab_companionProfileWatch" msgid="2457738382085872542">"የእጅ ሰዓቶችን ለማስተዳደር የአጃቢ የእጅ ሰዓት መገለጫ ፍቃድ"</string>
+ <string name="permdesc_companionProfileWatch" msgid="5655698581110449397">"አጃቢ መተግበሪያ የእጅ ሰዓቶችን እንዲያስተዳድር ያስችላል።"</string>
+ <string name="permlab_observeCompanionDevicePresence" msgid="9008994909653990465">"የአጃቢ መሣሪያ ተገኝነትን ተመልከት"</string>
+ <string name="permdesc_observeCompanionDevicePresence" msgid="3011699826788697852">"አጃቢ መተግበሪያ መሳሪያዎቹ በአቅራቢያ ሲሆኑ ወይም ሩቅ ሲሆኑ የአጃቢ መሳሪያ መኖሩን ለማየት ያስችላል።"</string>
+ <string name="permlab_deliverCompanionMessages" msgid="3931552294842980887">"አጃቢ መልዕክቶችን አድርስ"</string>
+ <string name="permdesc_deliverCompanionMessages" msgid="2170847384281412850">"አጃቢ መተግበሪያ አጃቢ መልዕክቶችን ወደ ሌሎች መሣሪያዎች እንዲያደርስ ያስችላል።"</string>
+ <string name="permlab_startForegroundServicesFromBackground" msgid="6363004936218638382">"የፊት አገልግሎቶችን ከዳራ ይጀምሩ"</string>
+ <string name="permdesc_startForegroundServicesFromBackground" msgid="4071826571656001537">"አጃቢ መተግበሪያ ከዳራ የፊት አገልግሎቶችን እንዲጀምር ያስችላል።"</string>
+ <!-- no translation found for mic_access_on_toast (2666925317663845156) -->
<skip />
- <!-- no translation found for permdesc_companionProfileWatch (5655698581110449397) -->
- <skip />
- <!-- no translation found for permlab_observeCompanionDevicePresence (9008994909653990465) -->
- <skip />
- <!-- no translation found for permdesc_observeCompanionDevicePresence (3011699826788697852) -->
- <skip />
- <!-- no translation found for permlab_deliverCompanionMessages (3931552294842980887) -->
- <skip />
- <!-- no translation found for permdesc_deliverCompanionMessages (2170847384281412850) -->
- <skip />
- <!-- no translation found for permlab_startForegroundServicesFromBackground (6363004936218638382) -->
- <skip />
- <!-- no translation found for permdesc_startForegroundServicesFromBackground (4071826571656001537) -->
+ <!-- no translation found for mic_access_off_toast (8111040892954242437) -->
<skip />
</resources>
diff --git a/core/res/res/values-ar/strings.xml b/core/res/res/values-ar/strings.xml
index 52381f9..ad1d34f 100644
--- a/core/res/res/values-ar/strings.xml
+++ b/core/res/res/values-ar/strings.xml
@@ -2356,4 +2356,8 @@
<string name="permdesc_deliverCompanionMessages" msgid="2170847384281412850">"يسمح هذا الإذن للتطبيق المصاحب بتسليم الرسائل المصاحبة إلى الأجهزة الأخرى."</string>
<string name="permlab_startForegroundServicesFromBackground" msgid="6363004936218638382">"بدء الخدمات التي تعمل في المقدّمة من الخلفية"</string>
<string name="permdesc_startForegroundServicesFromBackground" msgid="4071826571656001537">"يسمح هذا الإذن للتطبيق المصاحب ببدء الخدمات التي تعمل في المقدّمة من الخلفية."</string>
+ <!-- no translation found for mic_access_on_toast (2666925317663845156) -->
+ <skip />
+ <!-- no translation found for mic_access_off_toast (8111040892954242437) -->
+ <skip />
</resources>
diff --git a/core/res/res/values-as/strings.xml b/core/res/res/values-as/strings.xml
index 2b6debc..31b62be 100644
--- a/core/res/res/values-as/strings.xml
+++ b/core/res/res/values-as/strings.xml
@@ -2352,4 +2352,8 @@
<string name="permdesc_deliverCompanionMessages" msgid="2170847384281412850">"এটা সহযোগী এপক অন্য ডিভাইচলৈ সহযোগী বাৰ্তাসমূহ ডেলিভাৰ কৰিবলৈ দিয়ে।"</string>
<string name="permlab_startForegroundServicesFromBackground" msgid="6363004936218638382">"নেপথ্যৰ পৰা অগ্ৰভূমি সেৱাসমূহ আৰম্ভ কৰক"</string>
<string name="permdesc_startForegroundServicesFromBackground" msgid="4071826571656001537">"এটা সহযোগী এপক নেপথ্যৰ পৰা অগ্ৰভূমি সেৱাসমূহ আৰম্ভ কৰিবলৈ দিয়ে।"</string>
+ <!-- no translation found for mic_access_on_toast (2666925317663845156) -->
+ <skip />
+ <!-- no translation found for mic_access_off_toast (8111040892954242437) -->
+ <skip />
</resources>
diff --git a/core/res/res/values-az/strings.xml b/core/res/res/values-az/strings.xml
index b93eb9a..253a894 100644
--- a/core/res/res/values-az/strings.xml
+++ b/core/res/res/values-az/strings.xml
@@ -2352,4 +2352,8 @@
<string name="permdesc_deliverCompanionMessages" msgid="2170847384281412850">"Kompanyon tətbiqinə kompanyon mesajlarını digər cihazlara çatdırmaq icazəsi verir."</string>
<string name="permlab_startForegroundServicesFromBackground" msgid="6363004936218638382">"Ön fon xidmətlərini arxa fondan başlatmaq"</string>
<string name="permdesc_startForegroundServicesFromBackground" msgid="4071826571656001537">"Kompanyon tətbiqinə ön fon xidmətlərini arxa fondan başlatmaq icazəsi verir."</string>
+ <!-- no translation found for mic_access_on_toast (2666925317663845156) -->
+ <skip />
+ <!-- no translation found for mic_access_off_toast (8111040892954242437) -->
+ <skip />
</resources>
diff --git a/core/res/res/values-b+sr+Latn-television/strings.xml b/core/res/res/values-b+sr+Latn-television/strings.xml
index df5d8ea..8643593 100644
--- a/core/res/res/values-b+sr+Latn-television/strings.xml
+++ b/core/res/res/values-b+sr+Latn-television/strings.xml
@@ -17,6 +17,6 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="sensor_privacy_start_use_mic_notification_content_title" msgid="7002619958660406548">"Mikrofon je blokiran"</string>
- <string name="sensor_privacy_start_use_camera_notification_content_title" msgid="2131954635322568179">"Kamera je blokirana"</string>
+ <string name="sensor_privacy_start_use_mic_notification_content_title" msgid="7002619958660406548">"Микрофон је блокиран"</string>
+ <string name="sensor_privacy_start_use_camera_notification_content_title" msgid="2131954635322568179">"Камера је блокирана"</string>
</resources>
diff --git a/core/res/res/values-b+sr+Latn/strings.xml b/core/res/res/values-b+sr+Latn/strings.xml
index 217908e..562ffec 100644
--- a/core/res/res/values-b+sr+Latn/strings.xml
+++ b/core/res/res/values-b+sr+Latn/strings.xml
@@ -22,844 +22,844 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="byteShort" msgid="202579285008794431">"B"</string>
<string name="fileSizeSuffix" msgid="4233671691980131257">"<xliff:g id="NUMBER">%1$s</xliff:g> <xliff:g id="UNIT">%2$s</xliff:g>"</string>
- <string name="untitled" msgid="3381766946944136678">"<Bez imena>"</string>
- <string name="emptyPhoneNumber" msgid="5812172618020360048">"(Nema broja telefona)"</string>
- <string name="unknownName" msgid="7078697621109055330">"Nepoznato"</string>
- <string name="defaultVoiceMailAlphaTag" msgid="2190754495304236490">"Glasovna pošta"</string>
+ <string name="untitled" msgid="3381766946944136678">"<Без имена>"</string>
+ <string name="emptyPhoneNumber" msgid="5812172618020360048">"(Нема броја телефона)"</string>
+ <string name="unknownName" msgid="7078697621109055330">"Непознато"</string>
+ <string name="defaultVoiceMailAlphaTag" msgid="2190754495304236490">"Гласовна пошта"</string>
<string name="defaultMsisdnAlphaTag" msgid="2285034592902077488">"MSISDN1"</string>
- <string name="mmiError" msgid="2862759606579822246">"Problemi sa vezom ili nevažeći MMI kôd."</string>
- <string name="mmiErrorNotSupported" msgid="5001803469335286099">"Funkcija nije podržana."</string>
- <string name="mmiFdnError" msgid="3975490266767565852">"Rad je ograničen samo na brojeve fiksnog biranja."</string>
- <string name="mmiErrorWhileRoaming" msgid="1204173664713870114">"Ne možete da promenite podešavanja preusmeravanja poziva sa telefona dok ste u romingu."</string>
- <string name="serviceEnabled" msgid="7549025003394765639">"Usluga je omogućena."</string>
- <string name="serviceEnabledFor" msgid="1463104778656711613">"Usluga je omogućena za:"</string>
- <string name="serviceDisabled" msgid="641878791205871379">"Usluga je onemogućena."</string>
- <string name="serviceRegistered" msgid="3856192211729577482">"Registracija je uspela."</string>
- <string name="serviceErased" msgid="997354043770513494">"Brisanje je dovršeno."</string>
- <string name="passwordIncorrect" msgid="917087532676155877">"Neispravna lozinka."</string>
- <string name="mmiComplete" msgid="6341884570892520140">"MMI kôd je izvršen."</string>
- <string name="badPin" msgid="888372071306274355">"Stari PIN koji ste uneli nije tačan."</string>
- <string name="badPuk" msgid="4232069163733147376">"PUK koji ste uneli nije tačan."</string>
- <string name="mismatchPin" msgid="2929611853228707473">"PIN kodovi koje ste uneli se ne podudaraju."</string>
- <string name="invalidPin" msgid="7542498253319440408">"Otkucajte PIN koji ima od 4 do 8 brojeva."</string>
- <string name="invalidPuk" msgid="8831151490931907083">"Unesite PUK koji se sastoji od 8 cifara ili više."</string>
+ <string name="mmiError" msgid="2862759606579822246">"Проблеми са везом или неважећи MMI кôд."</string>
+ <string name="mmiErrorNotSupported" msgid="5001803469335286099">"Функција није подржана."</string>
+ <string name="mmiFdnError" msgid="3975490266767565852">"Рад је ограничен само на бројеве фиксног бирања."</string>
+ <string name="mmiErrorWhileRoaming" msgid="1204173664713870114">"Не можете да промените подешавања преусмеравања позива са телефона док сте у ромингу."</string>
+ <string name="serviceEnabled" msgid="7549025003394765639">"Услуга је омогућена."</string>
+ <string name="serviceEnabledFor" msgid="1463104778656711613">"Услуга је омогућена за:"</string>
+ <string name="serviceDisabled" msgid="641878791205871379">"Услуга је онемогућена."</string>
+ <string name="serviceRegistered" msgid="3856192211729577482">"Регистрација је успела."</string>
+ <string name="serviceErased" msgid="997354043770513494">"Брисање је довршено."</string>
+ <string name="passwordIncorrect" msgid="917087532676155877">"Неисправна лозинка."</string>
+ <string name="mmiComplete" msgid="6341884570892520140">"MMI кôд је извршен."</string>
+ <string name="badPin" msgid="888372071306274355">"Стари PIN који сте унели није тачан."</string>
+ <string name="badPuk" msgid="4232069163733147376">"PUK који сте унели није тачан."</string>
+ <string name="mismatchPin" msgid="2929611853228707473">"PIN кодови које сте унели се не подударају."</string>
+ <string name="invalidPin" msgid="7542498253319440408">"Откуцајте PIN који има од 4 до 8 бројева."</string>
+ <string name="invalidPuk" msgid="8831151490931907083">"Унесите PUK који се састоји од 8 цифара или више."</string>
<!-- no translation found for needPuk (3503414069503752211) -->
<skip />
<!-- no translation found for needPuk2 (3910763547447344963) -->
<skip />
- <string name="enablePin" msgid="2543771964137091212">"Nije uspelo. Omogućite zaključavanje SIM/RUIM kartice."</string>
+ <string name="enablePin" msgid="2543771964137091212">"Није успело. Омогућите закључавање SIM/RUIM картице."</string>
<plurals name="pinpuk_attempts" formatted="false" msgid="1619867269012213584">
- <item quantity="one">Imate još <xliff:g id="NUMBER_1">%d</xliff:g> pokušaj pre nego što se SIM kartica zaključa.</item>
- <item quantity="few">Imate još <xliff:g id="NUMBER_1">%d</xliff:g> pokušaja pre nego što se SIM kartica zaključa.</item>
- <item quantity="other">Imate još <xliff:g id="NUMBER_1">%d</xliff:g> pokušaja pre nego što se SIM kartica zaključa.</item>
+ <item quantity="one">Имате још <xliff:g id="NUMBER_1">%d</xliff:g> покушај пре него што се SIM картица закључа.</item>
+ <item quantity="few">Имате још <xliff:g id="NUMBER_1">%d</xliff:g> покушаја пре него што се SIM картица закључа.</item>
+ <item quantity="other">Имате још <xliff:g id="NUMBER_1">%d</xliff:g> покушаја пре него што се SIM картица закључа.</item>
</plurals>
<string name="imei" msgid="2157082351232630390">"IMEI"</string>
<string name="meid" msgid="3291227361605924674">"MEID"</string>
- <string name="ClipMmi" msgid="4110549342447630629">"Dolazni ID pozivaoca"</string>
- <string name="ClirMmi" msgid="6752346475055446417">"Sakrijte ID odlaznog pozivaoca"</string>
- <string name="ColpMmi" msgid="4736462893284419302">"ID povezane linije"</string>
- <string name="ColrMmi" msgid="5889782479745764278">"Ograničenje ID-a povezane linije"</string>
- <string name="CfMmi" msgid="8390012691099787178">"Preusmeravanje poziva"</string>
- <string name="CwMmi" msgid="3164609577675404761">"Poziv na čekanju"</string>
- <string name="BaMmi" msgid="7205614070543372167">"Ograničavanje poziva"</string>
- <string name="PwdMmi" msgid="3360991257288638281">"Promena lozinke"</string>
- <string name="PinMmi" msgid="7133542099618330959">"Promena PIN koda"</string>
- <string name="CnipMmi" msgid="4897531155968151160">"Pozivanje postojećeg broja"</string>
- <string name="CnirMmi" msgid="885292039284503036">"Pozivanje broja je ograničeno"</string>
- <string name="ThreeWCMmi" msgid="2436550866139999411">"Trosmerno pozivanje"</string>
- <string name="RuacMmi" msgid="1876047385848991110">"Odbijanje nepoželjnih poziva"</string>
- <string name="CndMmi" msgid="185136449405618437">"Isporuka broja za pozivanje"</string>
- <string name="DndMmi" msgid="8797375819689129800">"Ne uznemiravaj"</string>
- <string name="CLIRDefaultOnNextCallOn" msgid="4511621022859867988">"ID pozivaoca je podrazumevano ograničen. Sledeći poziv: ograničen."</string>
- <string name="CLIRDefaultOnNextCallOff" msgid="5036749051007098105">"ID pozivaoca je podrazumevano ograničen. Sledeći poziv: Nije ograničen."</string>
- <string name="CLIRDefaultOffNextCallOn" msgid="1022781126694885017">"ID pozivaoca podrazumevano nije ograničen. Sledeći poziv: ograničen."</string>
- <string name="CLIRDefaultOffNextCallOff" msgid="2491576172356463443">"ID pozivaoca podrazumevano nije ograničen. Sledeći poziv: Nije ograničen."</string>
- <string name="serviceNotProvisioned" msgid="8289333510236766193">"Usluga nije dobavljena."</string>
- <string name="CLIRPermanent" msgid="166443681876381118">"Ne možete da promenite podešavanje ID-a korisnika."</string>
- <string name="auto_data_switch_title" msgid="3286350716870518297">"Mobilni podaci su prebačeni na operatera <xliff:g id="CARRIERDISPLAY">%s</xliff:g>"</string>
- <string name="auto_data_switch_content" msgid="803557715007110959">"Ovo možete u svakom trenutku da promenite u Podešavanjima"</string>
- <string name="RestrictedOnDataTitle" msgid="1500576417268169774">"Nema usluge mobilnih podataka"</string>
- <string name="RestrictedOnEmergencyTitle" msgid="2852916906106191866">"Hitni pozivi nisu dostupni"</string>
- <string name="RestrictedOnNormalTitle" msgid="7009474589746551737">"Nema glasovne usluge"</string>
- <string name="RestrictedOnAllVoiceTitle" msgid="3982069078579103087">"Nema glasovne usluge ni hitnih poziva"</string>
- <string name="RestrictedStateContent" msgid="7693575344608618926">"Privremeno isključio mobilni operater"</string>
- <string name="RestrictedStateContentMsimTemplate" msgid="5228235722511044687">"Privremeno je isključio mobilni operater za SIM <xliff:g id="SIMNUMBER">%d</xliff:g>"</string>
- <string name="NetworkPreferenceSwitchTitle" msgid="1008329951315753038">"Povezivanje sa mobilnom mrežom nije uspelo"</string>
- <string name="NetworkPreferenceSwitchSummary" msgid="2086506181486324860">"Probajte da promenite željenu mrežu. Dodirnite da biste promenili."</string>
- <string name="EmergencyCallWarningTitle" msgid="1615688002899152860">"Hitni pozivi nisu dostupni"</string>
- <string name="EmergencyCallWarningSummary" msgid="1194185880092805497">"Ne možete da upućujete hitne pozive preko Wi‑Fi-ja"</string>
- <string name="notification_channel_network_alert" msgid="4788053066033851841">"Obaveštenja"</string>
- <string name="notification_channel_call_forward" msgid="8230490317314272406">"Preusmeravanje poziva"</string>
- <string name="notification_channel_emergency_callback" msgid="54074839059123159">"Režim za hitan povratni poziv"</string>
- <string name="notification_channel_mobile_data_status" msgid="1941911162076442474">"Status mobilnih podataka"</string>
- <string name="notification_channel_sms" msgid="1243384981025535724">"SMS-ovi"</string>
- <string name="notification_channel_voice_mail" msgid="8457433203106654172">"Poruke govorne pošte"</string>
- <string name="notification_channel_wfc" msgid="9048240466765169038">"Pozivanje preko WiFi mreže"</string>
- <string name="notification_channel_sim" msgid="5098802350325677490">"Status SIM-a"</string>
- <string name="notification_channel_sim_high_prio" msgid="642361929452850928">"Obaveštenja SIM kartice sa statusom „visok prioritet“"</string>
- <string name="peerTtyModeFull" msgid="337553730440832160">"Korisnik zahteva POTPUN režim TTY"</string>
- <string name="peerTtyModeHco" msgid="5626377160840915617">"Korisnik zahteva PRENOS ZVUKA za režim TTY"</string>
- <string name="peerTtyModeVco" msgid="572208600818270944">"Korisnik zahteva PRENOS GLASA za režim TTY"</string>
- <string name="peerTtyModeOff" msgid="2420380956369226583">"Korisnik zahteva ISKLJUČEN režim TTY"</string>
+ <string name="ClipMmi" msgid="4110549342447630629">"Долазни ИД позиваоца"</string>
+ <string name="ClirMmi" msgid="6752346475055446417">"Сакријте ИД одлазног позиваоца"</string>
+ <string name="ColpMmi" msgid="4736462893284419302">"ИД повезане линије"</string>
+ <string name="ColrMmi" msgid="5889782479745764278">"Ограничење ИД-а повезане линије"</string>
+ <string name="CfMmi" msgid="8390012691099787178">"Преусмеравање позива"</string>
+ <string name="CwMmi" msgid="3164609577675404761">"Позив на чекању"</string>
+ <string name="BaMmi" msgid="7205614070543372167">"Ограничавање позива"</string>
+ <string name="PwdMmi" msgid="3360991257288638281">"Промена лозинке"</string>
+ <string name="PinMmi" msgid="7133542099618330959">"Промена PIN кода"</string>
+ <string name="CnipMmi" msgid="4897531155968151160">"Позивање постојећег броја"</string>
+ <string name="CnirMmi" msgid="885292039284503036">"Позивање броја је ограничено"</string>
+ <string name="ThreeWCMmi" msgid="2436550866139999411">"Тросмерно позивање"</string>
+ <string name="RuacMmi" msgid="1876047385848991110">"Одбијање непожељних позива"</string>
+ <string name="CndMmi" msgid="185136449405618437">"Испорука броја за позивање"</string>
+ <string name="DndMmi" msgid="8797375819689129800">"Не узнемиравај"</string>
+ <string name="CLIRDefaultOnNextCallOn" msgid="4511621022859867988">"ИД позиваоца је подразумевано ограничен. Следећи позив: ограничен."</string>
+ <string name="CLIRDefaultOnNextCallOff" msgid="5036749051007098105">"ИД позиваоца је подразумевано ограничен. Следећи позив: Није ограничен."</string>
+ <string name="CLIRDefaultOffNextCallOn" msgid="1022781126694885017">"ИД позиваоца подразумевано није ограничен. Следећи позив: ограничен."</string>
+ <string name="CLIRDefaultOffNextCallOff" msgid="2491576172356463443">"ИД позиваоца подразумевано није ограничен. Следећи позив: Није ограничен."</string>
+ <string name="serviceNotProvisioned" msgid="8289333510236766193">"Услуга није добављена."</string>
+ <string name="CLIRPermanent" msgid="166443681876381118">"Не можете да промените подешавање ИД-а корисника."</string>
+ <string name="auto_data_switch_title" msgid="3286350716870518297">"Мобилни подаци су пребачени на оператера <xliff:g id="CARRIERDISPLAY">%s</xliff:g>"</string>
+ <string name="auto_data_switch_content" msgid="803557715007110959">"Ово можете у сваком тренутку да промените у Подешавањима"</string>
+ <string name="RestrictedOnDataTitle" msgid="1500576417268169774">"Нема услуге мобилних података"</string>
+ <string name="RestrictedOnEmergencyTitle" msgid="2852916906106191866">"Хитни позиви нису доступни"</string>
+ <string name="RestrictedOnNormalTitle" msgid="7009474589746551737">"Нема гласовне услуге"</string>
+ <string name="RestrictedOnAllVoiceTitle" msgid="3982069078579103087">"Нема гласовне услуге ни хитних позива"</string>
+ <string name="RestrictedStateContent" msgid="7693575344608618926">"Привремено искључио мобилни оператер"</string>
+ <string name="RestrictedStateContentMsimTemplate" msgid="5228235722511044687">"Привремено је искључио мобилни оператер за SIM <xliff:g id="SIMNUMBER">%d</xliff:g>"</string>
+ <string name="NetworkPreferenceSwitchTitle" msgid="1008329951315753038">"Повезивање са мобилном мрежом није успело"</string>
+ <string name="NetworkPreferenceSwitchSummary" msgid="2086506181486324860">"Пробајте да промените жељену мрежу. Додирните да бисте променили."</string>
+ <string name="EmergencyCallWarningTitle" msgid="1615688002899152860">"Хитни позиви нису доступни"</string>
+ <string name="EmergencyCallWarningSummary" msgid="1194185880092805497">"Не можете да упућујете хитне позиве преко Wi‑Fi-ја"</string>
+ <string name="notification_channel_network_alert" msgid="4788053066033851841">"Обавештења"</string>
+ <string name="notification_channel_call_forward" msgid="8230490317314272406">"Преусмеравање позива"</string>
+ <string name="notification_channel_emergency_callback" msgid="54074839059123159">"Режим за хитан повратни позив"</string>
+ <string name="notification_channel_mobile_data_status" msgid="1941911162076442474">"Статус мобилних података"</string>
+ <string name="notification_channel_sms" msgid="1243384981025535724">"SMS-ови"</string>
+ <string name="notification_channel_voice_mail" msgid="8457433203106654172">"Поруке говорне поште"</string>
+ <string name="notification_channel_wfc" msgid="9048240466765169038">"Позивање преко WiFi мреже"</string>
+ <string name="notification_channel_sim" msgid="5098802350325677490">"Статус SIM-а"</string>
+ <string name="notification_channel_sim_high_prio" msgid="642361929452850928">"Обавештења SIM картице са статусом „висок приоритет“"</string>
+ <string name="peerTtyModeFull" msgid="337553730440832160">"Корисник захтева ПОТПУН режим TTY"</string>
+ <string name="peerTtyModeHco" msgid="5626377160840915617">"Корисник захтева ПРЕНОС ЗВУКА за режим TTY"</string>
+ <string name="peerTtyModeVco" msgid="572208600818270944">"Корисник захтева ПРЕНОС ГЛАСА за режим TTY"</string>
+ <string name="peerTtyModeOff" msgid="2420380956369226583">"Корисник захтева ИСКЉУЧЕН режим TTY"</string>
<string name="serviceClassVoice" msgid="2065556932043454987">"Voice"</string>
- <string name="serviceClassData" msgid="4148080018967300248">"Podaci"</string>
- <string name="serviceClassFAX" msgid="2561653371698904118">"FAKS"</string>
+ <string name="serviceClassData" msgid="4148080018967300248">"Подаци"</string>
+ <string name="serviceClassFAX" msgid="2561653371698904118">"ФАКС"</string>
<string name="serviceClassSMS" msgid="1547664561704509004">"SMS"</string>
- <string name="serviceClassDataAsync" msgid="2029856900898545984">"Asinhroni podaci"</string>
- <string name="serviceClassDataSync" msgid="7895071363569133704">"Sinhronizovano"</string>
- <string name="serviceClassPacket" msgid="1430642951399303804">"Paket"</string>
- <string name="serviceClassPAD" msgid="6850244583416306321">"PODLOGA"</string>
- <string name="roamingText0" msgid="7793257871609854208">"Indikator rominga je uključen"</string>
- <string name="roamingText1" msgid="5073028598334616445">"Indikator rominga je isključen"</string>
- <string name="roamingText2" msgid="2834048284153110598">"Treperenje indikatora rominga"</string>
- <string name="roamingText3" msgid="831690234035748988">"Izvan komšiluka"</string>
- <string name="roamingText4" msgid="2171252529065590728">"Izvan zgrade"</string>
- <string name="roamingText5" msgid="4294671587635796641">"Roming – željeni sistem"</string>
- <string name="roamingText6" msgid="5536156746637992029">"Roming – dostupni sistem"</string>
- <string name="roamingText7" msgid="1783303085512907706">"Roming – partner"</string>
- <string name="roamingText8" msgid="7774800704373721973">"Roming – premijum partner"</string>
- <string name="roamingText9" msgid="1933460020190244004">"Roming – potpuno funkcionisanje usluge"</string>
- <string name="roamingText10" msgid="7434767033595769499">"Roming – delimično funkcionisanje usluge"</string>
- <string name="roamingText11" msgid="5245687407203281407">"Baner rominga je uključen"</string>
- <string name="roamingText12" msgid="673537506362152640">"Baner rominga je isključen"</string>
- <string name="roamingTextSearching" msgid="5323235489657753486">"Pretraživanje usluge"</string>
- <string name="wfcRegErrorTitle" msgid="3193072971584858020">"Podešavanje pozivanja preko WiFi-a nije uspelo"</string>
+ <string name="serviceClassDataAsync" msgid="2029856900898545984">"Асинхрони подаци"</string>
+ <string name="serviceClassDataSync" msgid="7895071363569133704">"Синхронизовано"</string>
+ <string name="serviceClassPacket" msgid="1430642951399303804">"Пакет"</string>
+ <string name="serviceClassPAD" msgid="6850244583416306321">"ПОДЛОГА"</string>
+ <string name="roamingText0" msgid="7793257871609854208">"Индикатор роминга је укључен"</string>
+ <string name="roamingText1" msgid="5073028598334616445">"Индикатор роминга је искључен"</string>
+ <string name="roamingText2" msgid="2834048284153110598">"Треперење индикатора роминга"</string>
+ <string name="roamingText3" msgid="831690234035748988">"Изван комшилука"</string>
+ <string name="roamingText4" msgid="2171252529065590728">"Изван зграде"</string>
+ <string name="roamingText5" msgid="4294671587635796641">"Роминг – жељени систем"</string>
+ <string name="roamingText6" msgid="5536156746637992029">"Роминг – доступни систем"</string>
+ <string name="roamingText7" msgid="1783303085512907706">"Роминг – партнер"</string>
+ <string name="roamingText8" msgid="7774800704373721973">"Роминг – премијум партнер"</string>
+ <string name="roamingText9" msgid="1933460020190244004">"Роминг – потпуно функционисање услуге"</string>
+ <string name="roamingText10" msgid="7434767033595769499">"Роминг – делимично функционисање услуге"</string>
+ <string name="roamingText11" msgid="5245687407203281407">"Банер роминга је укључен"</string>
+ <string name="roamingText12" msgid="673537506362152640">"Банер роминга је искључен"</string>
+ <string name="roamingTextSearching" msgid="5323235489657753486">"Претраживање услуге"</string>
+ <string name="wfcRegErrorTitle" msgid="3193072971584858020">"Подешавање позивања преко WiFi-а није успело"</string>
<string-array name="wfcOperatorErrorAlertMessages">
- <item msgid="468830943567116703">"Da biste upućivali pozive i slali poruke preko WiFi-a, prvo zatražite od mobilnog operatera da vam omogući ovu uslugu. Zatim u Podešavanjima ponovo uključite Pozivanje preko WiFi-a. (kôd greške: <xliff:g id="CODE">%1$s</xliff:g>)"</item>
+ <item msgid="468830943567116703">"Да бисте упућивали позиве и слали поруке преко WiFi-а, прво затражите од мобилног оператера да вам омогући ову услугу. Затим у Подешавањима поново укључите Позивање преко WiFi-а. (кôд грешке: <xliff:g id="CODE">%1$s</xliff:g>)"</item>
</string-array>
<string-array name="wfcOperatorErrorNotificationMessages">
- <item msgid="4795145070505729156">"Problem u vezi sa registrovanjem pozivanja preko Wi‑Fi-ja kod mobilnog operatera: <xliff:g id="CODE">%1$s</xliff:g>"</item>
+ <item msgid="4795145070505729156">"Проблем у вези са регистровањем позивања преко Wi‑Fi-ја код мобилног оператера: <xliff:g id="CODE">%1$s</xliff:g>"</item>
</string-array>
<!-- no translation found for wfcSpnFormat_spn (2982505428519096311) -->
<skip />
- <string name="wfcSpnFormat_spn_wifi_calling" msgid="3165949348000906194">"<xliff:g id="SPN">%s</xliff:g> pozivanje preko WiFi-a"</string>
- <string name="wfcSpnFormat_spn_wifi_calling_vo_hyphen" msgid="3836827895369365298">"<xliff:g id="SPN">%s</xliff:g> – pozivanje preko WiFi-a"</string>
- <string name="wfcSpnFormat_wlan_call" msgid="4895315549916165700">"WLAN poziv"</string>
- <string name="wfcSpnFormat_spn_wlan_call" msgid="255919245825481510">"<xliff:g id="SPN">%s</xliff:g> WLAN poziv"</string>
+ <string name="wfcSpnFormat_spn_wifi_calling" msgid="3165949348000906194">"<xliff:g id="SPN">%s</xliff:g> позивање преко WiFi-а"</string>
+ <string name="wfcSpnFormat_spn_wifi_calling_vo_hyphen" msgid="3836827895369365298">"<xliff:g id="SPN">%s</xliff:g> – позивање преко WiFi-а"</string>
+ <string name="wfcSpnFormat_wlan_call" msgid="4895315549916165700">"WLAN позив"</string>
+ <string name="wfcSpnFormat_spn_wlan_call" msgid="255919245825481510">"<xliff:g id="SPN">%s</xliff:g> WLAN позив"</string>
<string name="wfcSpnFormat_spn_wifi" msgid="7232899594327126970">"<xliff:g id="SPN">%s</xliff:g> WiFi"</string>
- <string name="wfcSpnFormat_wifi_calling_bar_spn" msgid="8383917598312067365">"Pozivanje preko WiFi-a | <xliff:g id="SPN">%s</xliff:g>"</string>
+ <string name="wfcSpnFormat_wifi_calling_bar_spn" msgid="8383917598312067365">"Позивање преко WiFi-а | <xliff:g id="SPN">%s</xliff:g>"</string>
<string name="wfcSpnFormat_spn_vowifi" msgid="6865214948822061486">"<xliff:g id="SPN">%s</xliff:g> VoWifi"</string>
- <string name="wfcSpnFormat_wifi_calling" msgid="6178935388378661755">"Pozivanje preko WiFi-a"</string>
+ <string name="wfcSpnFormat_wifi_calling" msgid="6178935388378661755">"Позивање преко WiFi-а"</string>
<string name="wfcSpnFormat_wifi" msgid="1376356951297043426">"WiFi"</string>
- <string name="wfcSpnFormat_wifi_calling_wo_hyphen" msgid="7178561009225028264">"Pozivanje preko WiFi-a"</string>
+ <string name="wfcSpnFormat_wifi_calling_wo_hyphen" msgid="7178561009225028264">"Позивање преко WiFi-а"</string>
<string name="wfcSpnFormat_vowifi" msgid="8371335230890725606">"VoWifi"</string>
- <string name="wifi_calling_off_summary" msgid="5626710010766902560">"Isključeno"</string>
- <string name="wfc_mode_wifi_preferred_summary" msgid="1035175836270943089">"Pozivanje preko WiFi-a"</string>
- <string name="wfc_mode_cellular_preferred_summary" msgid="4958965609212575619">"Poziv preko mobilne mreže"</string>
- <string name="wfc_mode_wifi_only_summary" msgid="104951993894678665">"Samo WiFi"</string>
+ <string name="wifi_calling_off_summary" msgid="5626710010766902560">"Искључено"</string>
+ <string name="wfc_mode_wifi_preferred_summary" msgid="1035175836270943089">"Позивање преко WiFi-а"</string>
+ <string name="wfc_mode_cellular_preferred_summary" msgid="4958965609212575619">"Позив преко мобилне мреже"</string>
+ <string name="wfc_mode_wifi_only_summary" msgid="104951993894678665">"Само WiFi"</string>
<!-- no translation found for crossSimFormat_spn (9125246077491634262) -->
<skip />
- <string name="crossSimFormat_spn_cross_sim_calling" msgid="5620807020002879057">"<xliff:g id="SPN">%s</xliff:g> rezervni način za pozivanje"</string>
- <string name="cfTemplateNotForwarded" msgid="862202427794270501">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: Nije prosleđeno"</string>
+ <string name="crossSimFormat_spn_cross_sim_calling" msgid="5620807020002879057">"<xliff:g id="SPN">%s</xliff:g> резервни начин за позивање"</string>
+ <string name="cfTemplateNotForwarded" msgid="862202427794270501">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: Није прослеђено"</string>
<string name="cfTemplateForwarded" msgid="9132506315842157860">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g>"</string>
- <string name="cfTemplateForwardedTime" msgid="735042369233323609">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g> nakon <xliff:g id="TIME_DELAY">{2}</xliff:g> sekunde/i"</string>
- <string name="cfTemplateRegistered" msgid="5619930473441550596">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: Nije prosleđeno"</string>
- <string name="cfTemplateRegisteredTime" msgid="5222794399642525045">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: Nije prosleđeno"</string>
- <string name="fcComplete" msgid="1080909484660507044">"Kôd funkcije je izvršen."</string>
- <string name="fcError" msgid="5325116502080221346">"Problemi sa vezom ili nevažeći kôd funkcije."</string>
- <string name="httpErrorOk" msgid="6206751415788256357">"Potvrdi"</string>
- <string name="httpError" msgid="3406003584150566720">"Došlo je do greške na mreži."</string>
- <string name="httpErrorLookup" msgid="3099834738227549349">"Nije moguće pronaći URL adresu"</string>
- <string name="httpErrorUnsupportedAuthScheme" msgid="3976195595501606787">"Šema potvrda autentičnosti sajta nije podržana."</string>
- <string name="httpErrorAuth" msgid="469553140922938968">"Nije moguće potvrditi autentičnost."</string>
- <string name="httpErrorProxyAuth" msgid="7229662162030113406">"Potvrda identiteta preko proksi servera nije uspela."</string>
- <string name="httpErrorConnect" msgid="3295081579893205617">"Nije moguće povezati se sa serverom."</string>
- <string name="httpErrorIO" msgid="3860318696166314490">"Nije moguće komunicirati sa serverom. Probajte ponovo kasnije."</string>
- <string name="httpErrorTimeout" msgid="7446272815190334204">"Veza sa serverom je istekla."</string>
- <string name="httpErrorRedirectLoop" msgid="8455757777509512098">"Stranica sadrži previše veza za preusmeravanje sa servera."</string>
- <string name="httpErrorUnsupportedScheme" msgid="2664108769858966374">"Protokol nije podržan."</string>
- <string name="httpErrorFailedSslHandshake" msgid="546319061228876290">"Nije moguće uspostaviti bezbednu vezu."</string>
- <string name="httpErrorBadUrl" msgid="754447723314832538">"Stranicu nije moguće otvoriti zato što je URL adresa nevažeća."</string>
- <string name="httpErrorFile" msgid="3400658466057744084">"Nije moguće pristupiti datoteci."</string>
- <string name="httpErrorFileNotFound" msgid="5191433324871147386">"Nije moguće pronaći traženu datoteku."</string>
- <string name="httpErrorTooManyRequests" msgid="2149677715552037198">"Previše zahteva se obrađuje. Probajte ponovo kasnije."</string>
- <string name="notification_title" msgid="5783748077084481121">"Greška pri prijavljivanju za <xliff:g id="ACCOUNT">%1$s</xliff:g>"</string>
- <string name="contentServiceSync" msgid="2341041749565687871">"Sinhronizacija"</string>
- <string name="contentServiceSyncNotificationTitle" msgid="5766411446676388623">"Sinhronizacija nije uspela"</string>
- <string name="contentServiceTooManyDeletesNotificationDesc" msgid="4562226280528716090">"Previše pokušaja brisanja sadržaja <xliff:g id="CONTENT_TYPE">%s</xliff:g>."</string>
- <string name="low_memory" product="tablet" msgid="5557552311566179924">"Memorija tableta je puna! Izbrišite neke datoteke da biste oslobodili prostor."</string>
- <string name="low_memory" product="watch" msgid="3479447988234030194">"Memorija sata je puna. Izbrišite neke datoteke da biste oslobodili prostor."</string>
- <string name="low_memory" product="tv" msgid="6663680413790323318">"Memorijski prostor na Android TV uređaju je pun. Izbrišite neke datoteke da biste oslobodili prostor."</string>
- <string name="low_memory" product="default" msgid="2539532364144025569">"Memorija telefona je puna! Izbrišite neke datoteke da biste oslobodili prostor."</string>
- <string name="ssl_ca_cert_warning" msgid="7233573909730048571">"{count,plural, =1{Instaliran je autoritet za izdavanje sertifikata}one{Instalirani su autoriteti za izdavanje sertifikata}few{Instalirani su autoriteti za izdavanje sertifikata}other{Instalirani su autoriteti za izdavanje sertifikata}}"</string>
- <string name="ssl_ca_cert_noti_by_unknown" msgid="4961102218216815242">"Od strane nepoznate treće strane"</string>
- <string name="ssl_ca_cert_noti_by_administrator" msgid="4564941950768783879">"Od strane administratora poslovnog profila"</string>
- <string name="ssl_ca_cert_noti_managed" msgid="217337232273211674">"Od strane <xliff:g id="MANAGING_DOMAIN">%s</xliff:g>"</string>
- <string name="work_profile_deleted" msgid="5891181538182009328">"Poslovni profil je izbrisan"</string>
- <string name="work_profile_deleted_details" msgid="3773706828364418016">"Aplikacija za administratore na poslovnom profilu nedostaje ili je oštećena. Zbog toga su poslovni profil i povezani podaci izbrisani. Obratite se administratoru za pomoć."</string>
- <string name="work_profile_deleted_description_dpm_wipe" msgid="2477244968924647232">"Poslovni profil više nije dostupan na ovom uređaju"</string>
- <string name="work_profile_deleted_reason_maximum_password_failure" msgid="1080323158315663167">"Previše pokušaja unosa lozinke"</string>
- <string name="device_ownership_relinquished" msgid="4080886992183195724">"Administrator je ustupio uređaj za ličnu upotrebu"</string>
- <string name="network_logging_notification_title" msgid="554983187553845004">"Uređajem se upravlja"</string>
- <string name="network_logging_notification_text" msgid="1327373071132562512">"Organizacija upravlja ovim uređajem i može da nadgleda mrežni saobraćaj. Dodirnite za detalje."</string>
- <string name="location_changed_notification_title" msgid="3620158742816699316">"Aplikacije mogu da pristupaju vašoj lokaciji"</string>
- <string name="location_changed_notification_text" msgid="7158423339982706912">"Obratite se IT administratoru da biste saznali više"</string>
- <string name="geofencing_service" msgid="3826902410740315456">"Usluga virtuelnog geografskog opsega"</string>
- <string name="country_detector" msgid="7023275114706088854">"Detektor zemlje"</string>
- <string name="location_service" msgid="2439187616018455546">"Usluga lokacije"</string>
- <string name="gnss_service" msgid="8907781262179951385">"GNSS usluga"</string>
- <string name="sensor_notification_service" msgid="7474531979178682676">"Usluga obaveštenja senzora"</string>
- <string name="twilight_service" msgid="8964898045693187224">"Usluga Sumrak"</string>
- <string name="gnss_time_update_service" msgid="9039489496037616095">"GNSS usluga za ažuriranje vremena"</string>
- <string name="device_policy_manager_service" msgid="5085762851388850332">"Usluga Menadžer smernica za uređaje"</string>
- <string name="music_recognition_manager_service" msgid="7481956037950276359">"Usluga Menadžer prepoznavanja muzike"</string>
- <string name="factory_reset_warning" msgid="6858705527798047809">"Uređaj će biti obrisan"</string>
- <string name="factory_reset_message" msgid="2657049595153992213">"Ne možete da koristite ovu aplikaciju za administratore. Uređaj će sada biti obrisan.\n\nAko imate pitanja, kontaktirajte administratora organizacije."</string>
- <string name="printing_disabled_by" msgid="3517499806528864633">"Štampanje je onemogućila aplikacija <xliff:g id="OWNER_APP">%s</xliff:g>."</string>
- <string name="personal_apps_suspension_title" msgid="7561416677884286600">"Uključite poslovni profil"</string>
- <string name="personal_apps_suspension_text" msgid="6115455688932935597">"Lične aplikacije su blokirane dok ne uključite poslovni profil"</string>
- <string name="personal_apps_suspension_soon_text" msgid="8123898693479590">"Lične aplikacije će biti blokirane: <xliff:g id="DATE">%1$s</xliff:g> u <xliff:g id="TIME">%2$s</xliff:g>. IT administrator ne dozvoljava da poslovni profil bude isključen duže od <xliff:g id="NUMBER">%3$d</xliff:g> dana."</string>
- <string name="personal_apps_suspended_turn_profile_on" msgid="2758012869627513689">"Uključi"</string>
- <string name="me" msgid="6207584824693813140">"Ja"</string>
- <string name="power_dialog" product="tablet" msgid="8333207765671417261">"Opcije za tablet"</string>
- <string name="power_dialog" product="tv" msgid="7792839006640933763">"Opcije Android TV-a"</string>
- <string name="power_dialog" product="default" msgid="1107775420270203046">"Opcije telefona"</string>
- <string name="silent_mode" msgid="8796112363642579333">"Nečujni režim"</string>
- <string name="turn_on_radio" msgid="2961717788170634233">"Uključi bežični signal"</string>
- <string name="turn_off_radio" msgid="7222573978109933360">"Isključi bežični signal"</string>
- <string name="screen_lock" msgid="2072642720826409809">"Zaključaj ekran"</string>
- <string name="power_off" msgid="4111692782492232778">"Ugasi"</string>
- <string name="silent_mode_silent" msgid="5079789070221150912">"Zvono je isključeno"</string>
- <string name="silent_mode_vibrate" msgid="8821830448369552678">"Vibracija zvona"</string>
- <string name="silent_mode_ring" msgid="6039011004781526678">"Zvono je uključeno"</string>
- <string name="reboot_to_update_title" msgid="2125818841916373708">"Android ažuriranje sistema"</string>
- <string name="reboot_to_update_prepare" msgid="6978842143587422365">"Ažuriranje se priprema…"</string>
- <string name="reboot_to_update_package" msgid="4644104795527534811">"Paket ažuriranja se obrađuje..."</string>
- <string name="reboot_to_update_reboot" msgid="4474726009984452312">"Ponovo se pokreće..."</string>
- <string name="reboot_to_reset_title" msgid="2226229680017882787">"Resetovanje na fabrička podešavanja"</string>
- <string name="reboot_to_reset_message" msgid="3347690497972074356">"Ponovo se pokreće..."</string>
- <string name="shutdown_progress" msgid="5017145516412657345">"Isključivanje…"</string>
- <string name="shutdown_confirm" product="tablet" msgid="2872769463279602432">"Tablet će se isključiti."</string>
- <string name="shutdown_confirm" product="tv" msgid="7975942887313518330">"Android TV uređaj će se ugasiti."</string>
- <string name="shutdown_confirm" product="watch" msgid="2977299851200240146">"Sat će se ugasiti."</string>
- <string name="shutdown_confirm" product="default" msgid="136816458966692315">"Telefon će se isključiti."</string>
- <string name="shutdown_confirm_question" msgid="796151167261608447">"Da li želite da isključite telefon?"</string>
- <string name="reboot_safemode_title" msgid="5853949122655346734">"Restartuj sistem u bezbednom režimu"</string>
- <string name="reboot_safemode_confirm" msgid="1658357874737219624">"Da li želite da ponovo pokrenete sistem u bezbednom režimu? Ovo će onemogućiti sve instalirane aplikacije nezavisnih proizvođača. One će biti vraćene kada ponovo pokrenete sistem."</string>
- <string name="recent_tasks_title" msgid="8183172372995396653">"Nedavno"</string>
- <string name="no_recent_tasks" msgid="9063946524312275906">"Nema nedavnih aplikacija."</string>
- <string name="global_actions" product="tablet" msgid="4412132498517933867">"Opcije za tablet"</string>
- <string name="global_actions" product="tv" msgid="3871763739487450369">"Opcije Android TV-a"</string>
- <string name="global_actions" product="default" msgid="6410072189971495460">"Opcije telefona"</string>
- <string name="global_action_lock" msgid="6949357274257655383">"Zaključaj ekran"</string>
- <string name="global_action_power_off" msgid="4404936470711393203">"Ugasi"</string>
- <string name="global_action_power_options" msgid="1185286119330160073">"Napajanje"</string>
- <string name="global_action_restart" msgid="4678451019561687074">"Restartuj"</string>
- <string name="global_action_emergency" msgid="1387617624177105088">"Hitan poziv"</string>
- <string name="global_action_bug_report" msgid="5127867163044170003">"Izveštaj o grešci"</string>
- <string name="global_action_logout" msgid="6093581310002476511">"Završi sesiju"</string>
- <string name="global_action_screenshot" msgid="2610053466156478564">"Snimak ekrana"</string>
- <string name="bugreport_title" msgid="8549990811777373050">"Izveštaj o grešci"</string>
- <string name="bugreport_message" msgid="5212529146119624326">"Ovim će se prikupiti informacije o trenutnom stanju uređaja kako bi bile poslate u poruci e-pošte. Od započinjanja izveštaja o grešci do trenutka za njegovo slanje proći će neko vreme; budite strpljivi."</string>
- <string name="bugreport_option_interactive_title" msgid="7968287837902871289">"Interaktiv. izveštaj"</string>
- <string name="bugreport_option_interactive_summary" msgid="8493795476325339542">"Koristite ovo u većini slučajeva. To vam omogućava da pratite napredak izveštaja, da unosite dodatne detalje o problemu i da snimate snimke ekrana. Verovatno će izostaviti neke manje korišćene odeljke za koje pravljenje izveštaja dugo traje."</string>
- <string name="bugreport_option_full_title" msgid="7681035745950045690">"Kompletan izveštaj"</string>
- <string name="bugreport_option_full_summary" msgid="1975130009258435885">"Koristite ovu opciju radi minimalnih sistemskih smetnji kada uređaj ne reaguje, prespor je ili su vam potrebni svi odeljci izveštaja. Ne dozvoljava vam unos dodatnih detalja niti snimanje dodatnih snimaka ekrana."</string>
- <string name="bugreport_countdown" msgid="6418620521782120755">"{count,plural, =1{Napravićemo snimak ekrana radi izveštaja o grešci za # sekundu.}one{Napravićemo snimak ekrana radi izveštaja o grešci za # sekundu.}few{Napravićemo snimak ekrana radi izveštaja o grešci za # sekunde.}other{Napravićemo snimak ekrana radi izveštaja o grešci za # sekundi.}}"</string>
- <string name="bugreport_screenshot_success_toast" msgid="7986095104151473745">"Ekran sa izveštajem o grešci je snimljen"</string>
- <string name="bugreport_screenshot_failure_toast" msgid="6736320861311294294">"Snimanje ekrana sa izveštajem o grešci nije uspelo"</string>
- <string name="global_action_toggle_silent_mode" msgid="8464352592860372188">"Nečujni režim"</string>
- <string name="global_action_silent_mode_on_status" msgid="2371892537738632013">"Zvuk je ISKLJUČEN"</string>
- <string name="global_action_silent_mode_off_status" msgid="6608006545950920042">"Zvuk je UKLJUČEN"</string>
- <string name="global_actions_toggle_airplane_mode" msgid="6911684460146916206">"Režim rada u avionu"</string>
- <string name="global_actions_airplane_mode_on_status" msgid="5508025516695361936">"Režim rada u avionu je UKLJUČEN"</string>
- <string name="global_actions_airplane_mode_off_status" msgid="8522219771500505475">"Režim rada u avionu je ISKLJUČEN"</string>
- <string name="global_action_settings" msgid="4671878836947494217">"Podešavanja"</string>
- <string name="global_action_assist" msgid="2517047220311505805">"Pomoć"</string>
- <string name="global_action_voice_assist" msgid="6655788068555086695">"Glasovna pomoć"</string>
- <string name="global_action_lockdown" msgid="2475471405907902963">"Zaključavanje"</string>
+ <string name="cfTemplateForwardedTime" msgid="735042369233323609">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g> након <xliff:g id="TIME_DELAY">{2}</xliff:g> секунде/и"</string>
+ <string name="cfTemplateRegistered" msgid="5619930473441550596">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: Није прослеђено"</string>
+ <string name="cfTemplateRegisteredTime" msgid="5222794399642525045">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: Није прослеђено"</string>
+ <string name="fcComplete" msgid="1080909484660507044">"Кôд функције је извршен."</string>
+ <string name="fcError" msgid="5325116502080221346">"Проблеми са везом или неважећи кôд функције."</string>
+ <string name="httpErrorOk" msgid="6206751415788256357">"Потврди"</string>
+ <string name="httpError" msgid="3406003584150566720">"Дошло је до грешке на мрежи."</string>
+ <string name="httpErrorLookup" msgid="3099834738227549349">"Није могуће пронаћи URL адресу"</string>
+ <string name="httpErrorUnsupportedAuthScheme" msgid="3976195595501606787">"Шема потврда аутентичности сајта није подржана."</string>
+ <string name="httpErrorAuth" msgid="469553140922938968">"Није могуће потврдити аутентичност."</string>
+ <string name="httpErrorProxyAuth" msgid="7229662162030113406">"Потврда идентитета преко прокси сервера није успела."</string>
+ <string name="httpErrorConnect" msgid="3295081579893205617">"Није могуће повезати се са сервером."</string>
+ <string name="httpErrorIO" msgid="3860318696166314490">"Није могуће комуницирати са сервером. Пробајте поново касније."</string>
+ <string name="httpErrorTimeout" msgid="7446272815190334204">"Веза са сервером је истекла."</string>
+ <string name="httpErrorRedirectLoop" msgid="8455757777509512098">"Страница садржи превише веза за преусмеравање са сервера."</string>
+ <string name="httpErrorUnsupportedScheme" msgid="2664108769858966374">"Протокол није подржан."</string>
+ <string name="httpErrorFailedSslHandshake" msgid="546319061228876290">"Није могуће успоставити безбедну везу."</string>
+ <string name="httpErrorBadUrl" msgid="754447723314832538">"Страницу није могуће отворити зато што је URL адреса неважећа."</string>
+ <string name="httpErrorFile" msgid="3400658466057744084">"Није могуће приступити датотеци."</string>
+ <string name="httpErrorFileNotFound" msgid="5191433324871147386">"Није могуће пронаћи тражену датотеку."</string>
+ <string name="httpErrorTooManyRequests" msgid="2149677715552037198">"Превише захтева се обрађује. Пробајте поново касније."</string>
+ <string name="notification_title" msgid="5783748077084481121">"Грешка при пријављивању за <xliff:g id="ACCOUNT">%1$s</xliff:g>"</string>
+ <string name="contentServiceSync" msgid="2341041749565687871">"Синхронизација"</string>
+ <string name="contentServiceSyncNotificationTitle" msgid="5766411446676388623">"Синхронизација није успела"</string>
+ <string name="contentServiceTooManyDeletesNotificationDesc" msgid="4562226280528716090">"Превише покушаја брисања садржаја <xliff:g id="CONTENT_TYPE">%s</xliff:g>."</string>
+ <string name="low_memory" product="tablet" msgid="5557552311566179924">"Меморија таблета је пуна! Избришите неке датотеке да бисте ослободили простор."</string>
+ <string name="low_memory" product="watch" msgid="3479447988234030194">"Меморија сата је пуна. Избришите неке датотеке да бисте ослободили простор."</string>
+ <string name="low_memory" product="tv" msgid="6663680413790323318">"Меморијски простор на Android TV уређају је пун. Избришите неке датотеке да бисте ослободили простор."</string>
+ <string name="low_memory" product="default" msgid="2539532364144025569">"Меморија телефона је пуна! Избришите неке датотеке да бисте ослободили простор."</string>
+ <string name="ssl_ca_cert_warning" msgid="7233573909730048571">"{count,plural, =1{Инсталиран је ауторитет за издавање сертификата}one{Инсталирани су ауторитети за издавање сертификата}few{Инсталирани су ауторитети за издавање сертификата}other{Инсталирани су ауторитети за издавање сертификата}}"</string>
+ <string name="ssl_ca_cert_noti_by_unknown" msgid="4961102218216815242">"Од стране непознате треће стране"</string>
+ <string name="ssl_ca_cert_noti_by_administrator" msgid="4564941950768783879">"Од стране администратора пословног профила"</string>
+ <string name="ssl_ca_cert_noti_managed" msgid="217337232273211674">"Од стране <xliff:g id="MANAGING_DOMAIN">%s</xliff:g>"</string>
+ <string name="work_profile_deleted" msgid="5891181538182009328">"Пословни профил је избрисан"</string>
+ <string name="work_profile_deleted_details" msgid="3773706828364418016">"Апликација за администраторе на пословном профилу недостаје или је оштећена. Због тога су пословни профил и повезани подаци избрисани. Обратите се администратору за помоћ."</string>
+ <string name="work_profile_deleted_description_dpm_wipe" msgid="2477244968924647232">"Пословни профил више није доступан на овом уређају"</string>
+ <string name="work_profile_deleted_reason_maximum_password_failure" msgid="1080323158315663167">"Превише покушаја уноса лозинке"</string>
+ <string name="device_ownership_relinquished" msgid="4080886992183195724">"Администратор је уступио уређај за личну употребу"</string>
+ <string name="network_logging_notification_title" msgid="554983187553845004">"Уређајем се управља"</string>
+ <string name="network_logging_notification_text" msgid="1327373071132562512">"Организација управља овим уређајем и може да надгледа мрежни саобраћај. Додирните за детаље."</string>
+ <string name="location_changed_notification_title" msgid="3620158742816699316">"Апликације могу да приступају вашој локацији"</string>
+ <string name="location_changed_notification_text" msgid="7158423339982706912">"Обратите се ИТ администратору да бисте сазнали више"</string>
+ <string name="geofencing_service" msgid="3826902410740315456">"Услуга виртуелног географског опсега"</string>
+ <string name="country_detector" msgid="7023275114706088854">"Детектор земље"</string>
+ <string name="location_service" msgid="2439187616018455546">"Услуга локације"</string>
+ <string name="gnss_service" msgid="8907781262179951385">"GNSS услуга"</string>
+ <string name="sensor_notification_service" msgid="7474531979178682676">"Услуга обавештења сензора"</string>
+ <string name="twilight_service" msgid="8964898045693187224">"Услуга Сумрак"</string>
+ <string name="gnss_time_update_service" msgid="9039489496037616095">"GNSS услуга за ажурирање времена"</string>
+ <string name="device_policy_manager_service" msgid="5085762851388850332">"Услуга Менаџер смерница за уређаје"</string>
+ <string name="music_recognition_manager_service" msgid="7481956037950276359">"Услуга Менаџер препознавања музике"</string>
+ <string name="factory_reset_warning" msgid="6858705527798047809">"Уређај ће бити обрисан"</string>
+ <string name="factory_reset_message" msgid="2657049595153992213">"Не можете да користите ову апликацију за администраторе. Уређај ће сада бити обрисан.\n\nАко имате питања, контактирајте администратора организације."</string>
+ <string name="printing_disabled_by" msgid="3517499806528864633">"Штампање је онемогућила апликација <xliff:g id="OWNER_APP">%s</xliff:g>."</string>
+ <string name="personal_apps_suspension_title" msgid="7561416677884286600">"Укључите пословни профил"</string>
+ <string name="personal_apps_suspension_text" msgid="6115455688932935597">"Личне апликације су блокиране док не укључите пословни профил"</string>
+ <string name="personal_apps_suspension_soon_text" msgid="8123898693479590">"Личне апликације ће бити блокиране: <xliff:g id="DATE">%1$s</xliff:g> у <xliff:g id="TIME">%2$s</xliff:g>. ИТ администратор не дозвољава да пословни профил буде искључен дуже од <xliff:g id="NUMBER">%3$d</xliff:g> дана."</string>
+ <string name="personal_apps_suspended_turn_profile_on" msgid="2758012869627513689">"Укључи"</string>
+ <string name="me" msgid="6207584824693813140">"Ја"</string>
+ <string name="power_dialog" product="tablet" msgid="8333207765671417261">"Опције за таблет"</string>
+ <string name="power_dialog" product="tv" msgid="7792839006640933763">"Опције Android TV-а"</string>
+ <string name="power_dialog" product="default" msgid="1107775420270203046">"Опције телефона"</string>
+ <string name="silent_mode" msgid="8796112363642579333">"Нечујни режим"</string>
+ <string name="turn_on_radio" msgid="2961717788170634233">"Укључи бежични сигнал"</string>
+ <string name="turn_off_radio" msgid="7222573978109933360">"Искључи бежични сигнал"</string>
+ <string name="screen_lock" msgid="2072642720826409809">"Закључај екран"</string>
+ <string name="power_off" msgid="4111692782492232778">"Угаси"</string>
+ <string name="silent_mode_silent" msgid="5079789070221150912">"Звоно је искључено"</string>
+ <string name="silent_mode_vibrate" msgid="8821830448369552678">"Вибрација звона"</string>
+ <string name="silent_mode_ring" msgid="6039011004781526678">"Звоно је укључено"</string>
+ <string name="reboot_to_update_title" msgid="2125818841916373708">"Android ажурирање система"</string>
+ <string name="reboot_to_update_prepare" msgid="6978842143587422365">"Ажурирање се припрема…"</string>
+ <string name="reboot_to_update_package" msgid="4644104795527534811">"Пакет ажурирања се обрађује..."</string>
+ <string name="reboot_to_update_reboot" msgid="4474726009984452312">"Поново се покреће..."</string>
+ <string name="reboot_to_reset_title" msgid="2226229680017882787">"Ресетовање на фабричка подешавања"</string>
+ <string name="reboot_to_reset_message" msgid="3347690497972074356">"Поново се покреће..."</string>
+ <string name="shutdown_progress" msgid="5017145516412657345">"Искључивање…"</string>
+ <string name="shutdown_confirm" product="tablet" msgid="2872769463279602432">"Таблет ће се искључити."</string>
+ <string name="shutdown_confirm" product="tv" msgid="7975942887313518330">"Android TV уређај ће се угасити."</string>
+ <string name="shutdown_confirm" product="watch" msgid="2977299851200240146">"Сат ће се угасити."</string>
+ <string name="shutdown_confirm" product="default" msgid="136816458966692315">"Телефон ће се искључити."</string>
+ <string name="shutdown_confirm_question" msgid="796151167261608447">"Да ли желите да искључите телефон?"</string>
+ <string name="reboot_safemode_title" msgid="5853949122655346734">"Рестартуј систем у безбедном режиму"</string>
+ <string name="reboot_safemode_confirm" msgid="1658357874737219624">"Да ли желите да поново покренете систем у безбедном режиму? Ово ће онемогућити све инсталиране апликације независних произвођача. Оне ће бити враћене када поново покренете систем."</string>
+ <string name="recent_tasks_title" msgid="8183172372995396653">"Недавно"</string>
+ <string name="no_recent_tasks" msgid="9063946524312275906">"Нема недавних апликација."</string>
+ <string name="global_actions" product="tablet" msgid="4412132498517933867">"Опције за таблет"</string>
+ <string name="global_actions" product="tv" msgid="3871763739487450369">"Опције Android TV-а"</string>
+ <string name="global_actions" product="default" msgid="6410072189971495460">"Опције телефона"</string>
+ <string name="global_action_lock" msgid="6949357274257655383">"Закључај екран"</string>
+ <string name="global_action_power_off" msgid="4404936470711393203">"Угаси"</string>
+ <string name="global_action_power_options" msgid="1185286119330160073">"Напајање"</string>
+ <string name="global_action_restart" msgid="4678451019561687074">"Рестартуј"</string>
+ <string name="global_action_emergency" msgid="1387617624177105088">"Хитан позив"</string>
+ <string name="global_action_bug_report" msgid="5127867163044170003">"Извештај о грешци"</string>
+ <string name="global_action_logout" msgid="6093581310002476511">"Заврши сесију"</string>
+ <string name="global_action_screenshot" msgid="2610053466156478564">"Снимак екрана"</string>
+ <string name="bugreport_title" msgid="8549990811777373050">"Извештај о грешци"</string>
+ <string name="bugreport_message" msgid="5212529146119624326">"Овим ће се прикупити информације о тренутном стању уређаја како би биле послате у поруци е-поште. Од започињања извештаја о грешци до тренутка за његово слање проћи ће неко време; будите стрпљиви."</string>
+ <string name="bugreport_option_interactive_title" msgid="7968287837902871289">"Интерактив. извештај"</string>
+ <string name="bugreport_option_interactive_summary" msgid="8493795476325339542">"Користите ово у већини случајева. То вам омогућава да пратите напредак извештаја, да уносите додатне детаље о проблему и да снимате снимке екрана. Вероватно ће изоставити неке мање коришћене одељке за које прављење извештаја дуго траје."</string>
+ <string name="bugreport_option_full_title" msgid="7681035745950045690">"Комплетан извештај"</string>
+ <string name="bugreport_option_full_summary" msgid="1975130009258435885">"Користите ову опцију ради минималних системских сметњи када уређај не реагује, преспор је или су вам потребни сви одељци извештаја. Не дозвољава вам унос додатних детаља нити снимање додатних снимака екрана."</string>
+ <string name="bugreport_countdown" msgid="6418620521782120755">"{count,plural, =1{Направићемо снимак екрана ради извештаја о грешци за # секунду.}one{Направићемо снимак екрана ради извештаја о грешци за # секунду.}few{Направићемо снимак екрана ради извештаја о грешци за # секунде.}other{Направићемо снимак екрана ради извештаја о грешци за # секунди.}}"</string>
+ <string name="bugreport_screenshot_success_toast" msgid="7986095104151473745">"Екран са извештајем о грешци је снимљен"</string>
+ <string name="bugreport_screenshot_failure_toast" msgid="6736320861311294294">"Снимање екрана са извештајем о грешци није успело"</string>
+ <string name="global_action_toggle_silent_mode" msgid="8464352592860372188">"Нечујни режим"</string>
+ <string name="global_action_silent_mode_on_status" msgid="2371892537738632013">"Звук је ИСКЉУЧЕН"</string>
+ <string name="global_action_silent_mode_off_status" msgid="6608006545950920042">"Звук је УКЉУЧЕН"</string>
+ <string name="global_actions_toggle_airplane_mode" msgid="6911684460146916206">"Режим рада у авиону"</string>
+ <string name="global_actions_airplane_mode_on_status" msgid="5508025516695361936">"Режим рада у авиону је УКЉУЧЕН"</string>
+ <string name="global_actions_airplane_mode_off_status" msgid="8522219771500505475">"Режим рада у авиону је ИСКЉУЧЕН"</string>
+ <string name="global_action_settings" msgid="4671878836947494217">"Подешавања"</string>
+ <string name="global_action_assist" msgid="2517047220311505805">"Помоћ"</string>
+ <string name="global_action_voice_assist" msgid="6655788068555086695">"Гласовна помоћ"</string>
+ <string name="global_action_lockdown" msgid="2475471405907902963">"Закључавање"</string>
<string name="status_bar_notification_info_overflow" msgid="3330152558746563475">"999+"</string>
- <string name="notification_hidden_text" msgid="2835519769868187223">"Novo obaveštenje"</string>
- <string name="notification_channel_physical_keyboard" msgid="5417306456125988096">"Fizička tastatura"</string>
- <string name="notification_channel_security" msgid="8516754650348238057">"Bezbednost"</string>
- <string name="notification_channel_car_mode" msgid="2123919247040988436">"Režim rada u automobilu"</string>
- <string name="notification_channel_account" msgid="6436294521740148173">"Status naloga"</string>
- <string name="notification_channel_developer" msgid="1691059964407549150">"Poruke za programere"</string>
- <string name="notification_channel_developer_important" msgid="7197281908918789589">"Važne poruke programera"</string>
- <string name="notification_channel_updates" msgid="7907863984825495278">"Ažuriranja"</string>
- <string name="notification_channel_network_status" msgid="2127687368725272809">"Status mreže"</string>
- <string name="notification_channel_network_alerts" msgid="6312366315654526528">"Obaveštenja u vezi sa mrežom"</string>
- <string name="notification_channel_network_available" msgid="6083697929214165169">"Mreža je dostupna"</string>
- <string name="notification_channel_vpn" msgid="1628529026203808999">"Status VPN-a"</string>
- <string name="notification_channel_device_admin" msgid="6384932669406095506">"Obaveštenja od IT administratora"</string>
- <string name="notification_channel_alerts" msgid="5070241039583668427">"Obaveštenja"</string>
- <string name="notification_channel_retail_mode" msgid="3732239154256431213">"Režim demonstracije za maloprodajne objekte"</string>
- <string name="notification_channel_usb" msgid="1528280969406244896">"USB veza"</string>
- <string name="notification_channel_heavy_weight_app" msgid="17455756500828043">"Aktivna aplikacija"</string>
- <string name="notification_channel_foreground_service" msgid="7102189948158885178">"Aplikacije koje troše bateriju"</string>
- <string name="notification_channel_accessibility_magnification" msgid="1707913872219798098">"Uvećanje"</string>
- <string name="notification_channel_accessibility_security_policy" msgid="1727787021725251912">"Korišćenje Pristupačnosti"</string>
- <string name="foreground_service_app_in_background" msgid="1439289699671273555">"<xliff:g id="APP_NAME">%1$s</xliff:g> koristi bateriju"</string>
- <string name="foreground_service_apps_in_background" msgid="7340037176412387863">"Aplikacije (<xliff:g id="NUMBER">%1$d</xliff:g>) koriste bateriju"</string>
- <string name="foreground_service_tap_for_details" msgid="9078123626015586751">"Dodirnite za detalje o bateriji i potrošnji podataka"</string>
+ <string name="notification_hidden_text" msgid="2835519769868187223">"Ново обавештење"</string>
+ <string name="notification_channel_physical_keyboard" msgid="5417306456125988096">"Физичка тастатура"</string>
+ <string name="notification_channel_security" msgid="8516754650348238057">"Безбедност"</string>
+ <string name="notification_channel_car_mode" msgid="2123919247040988436">"Режим рада у аутомобилу"</string>
+ <string name="notification_channel_account" msgid="6436294521740148173">"Статус налога"</string>
+ <string name="notification_channel_developer" msgid="1691059964407549150">"Поруке за програмере"</string>
+ <string name="notification_channel_developer_important" msgid="7197281908918789589">"Важне поруке програмера"</string>
+ <string name="notification_channel_updates" msgid="7907863984825495278">"Ажурирања"</string>
+ <string name="notification_channel_network_status" msgid="2127687368725272809">"Статус мреже"</string>
+ <string name="notification_channel_network_alerts" msgid="6312366315654526528">"Обавештења у вези са мрежом"</string>
+ <string name="notification_channel_network_available" msgid="6083697929214165169">"Мрежа је доступна"</string>
+ <string name="notification_channel_vpn" msgid="1628529026203808999">"Статус VPN-а"</string>
+ <string name="notification_channel_device_admin" msgid="6384932669406095506">"Обавештења од ИТ администратора"</string>
+ <string name="notification_channel_alerts" msgid="5070241039583668427">"Обавештења"</string>
+ <string name="notification_channel_retail_mode" msgid="3732239154256431213">"Режим демонстрације за малопродајне објекте"</string>
+ <string name="notification_channel_usb" msgid="1528280969406244896">"USB веза"</string>
+ <string name="notification_channel_heavy_weight_app" msgid="17455756500828043">"Активна апликација"</string>
+ <string name="notification_channel_foreground_service" msgid="7102189948158885178">"Апликације које троше батерију"</string>
+ <string name="notification_channel_accessibility_magnification" msgid="1707913872219798098">"Увећање"</string>
+ <string name="notification_channel_accessibility_security_policy" msgid="1727787021725251912">"Коришћење Приступачности"</string>
+ <string name="foreground_service_app_in_background" msgid="1439289699671273555">"<xliff:g id="APP_NAME">%1$s</xliff:g> користи батерију"</string>
+ <string name="foreground_service_apps_in_background" msgid="7340037176412387863">"Апликације (<xliff:g id="NUMBER">%1$d</xliff:g>) користе батерију"</string>
+ <string name="foreground_service_tap_for_details" msgid="9078123626015586751">"Додирните за детаље о батерији и потрошњи података"</string>
<string name="foreground_service_multiple_separator" msgid="5002287361849863168">"<xliff:g id="LEFT_SIDE">%1$s</xliff:g>, <xliff:g id="RIGHT_SIDE">%2$s</xliff:g>"</string>
- <string name="safeMode" msgid="8974401416068943888">"Bezbedni režim"</string>
- <string name="android_system_label" msgid="5974767339591067210">"Android sistem"</string>
- <string name="user_owner_label" msgid="8628726904184471211">"Pređi na lični profil"</string>
- <string name="managed_profile_label" msgid="7316778766973512382">"Pređi na poslovni profil"</string>
- <string name="permgrouplab_contacts" msgid="4254143639307316920">"Kontakti"</string>
- <string name="permgroupdesc_contacts" msgid="9163927941244182567">"pristupi kontaktima"</string>
- <string name="permgrouplab_location" msgid="1858277002233964394">"Lokacija"</string>
- <string name="permgroupdesc_location" msgid="1995955142118450685">"pristupi lokaciji ovog uređaja"</string>
- <string name="permgrouplab_calendar" msgid="6426860926123033230">"Kalendar"</string>
- <string name="permgroupdesc_calendar" msgid="6762751063361489379">"pristupi kalendaru"</string>
+ <string name="safeMode" msgid="8974401416068943888">"Безбедни режим"</string>
+ <string name="android_system_label" msgid="5974767339591067210">"Android систем"</string>
+ <string name="user_owner_label" msgid="8628726904184471211">"Пређи на лични профил"</string>
+ <string name="managed_profile_label" msgid="7316778766973512382">"Пређи на пословни профил"</string>
+ <string name="permgrouplab_contacts" msgid="4254143639307316920">"Контакти"</string>
+ <string name="permgroupdesc_contacts" msgid="9163927941244182567">"приступи контактима"</string>
+ <string name="permgrouplab_location" msgid="1858277002233964394">"Локација"</string>
+ <string name="permgroupdesc_location" msgid="1995955142118450685">"приступи локацији овог уређаја"</string>
+ <string name="permgrouplab_calendar" msgid="6426860926123033230">"Календар"</string>
+ <string name="permgroupdesc_calendar" msgid="6762751063361489379">"приступи календару"</string>
<string name="permgrouplab_sms" msgid="795737735126084874">"SMS"</string>
- <string name="permgroupdesc_sms" msgid="5726462398070064542">"šalje i pregleda SMS poruke"</string>
- <string name="permgrouplab_storage" msgid="17339216290379241">"Fajlovi"</string>
- <string name="permgroupdesc_storage" msgid="5378659041354582769">"pristup fajlovima na uređaju"</string>
- <string name="permgrouplab_readMediaAural" msgid="1858331312624942053">"Muzika i zvuk"</string>
- <string name="permgroupdesc_readMediaAural" msgid="7565467343667089595">"pristup muzici i audio sadržaju na uređaju"</string>
- <string name="permgrouplab_readMediaVisual" msgid="4724874717811908660">"Slike i video snimci"</string>
- <string name="permgroupdesc_readMediaVisual" msgid="4080463241903508688">"pristup slikama i video snimcima na uređaju"</string>
- <string name="permgrouplab_microphone" msgid="2480597427667420076">"Mikrofon"</string>
- <string name="permgroupdesc_microphone" msgid="1047786732792487722">"snima zvuk"</string>
- <string name="permgrouplab_activityRecognition" msgid="3324466667921775766">"Fizičke aktivnosti"</string>
- <string name="permgroupdesc_activityRecognition" msgid="4725624819457670704">"pristup fizičkim aktivnostima"</string>
- <string name="permgrouplab_camera" msgid="9090413408963547706">"Kamera"</string>
- <string name="permgroupdesc_camera" msgid="7585150538459320326">"snima slike i video"</string>
- <string name="permgrouplab_nearby_devices" msgid="5529147543651181991">"Uređaji u blizini"</string>
- <string name="permgroupdesc_nearby_devices" msgid="3213561597116913508">"otkrivanje uređaja u blizini i povezivanje sa njima"</string>
- <string name="permgrouplab_calllog" msgid="7926834372073550288">"Evidencije poziva"</string>
- <string name="permgroupdesc_calllog" msgid="2026996642917801803">"čitanje i pisanje evidencije poziva na telefonu"</string>
- <string name="permgrouplab_phone" msgid="570318944091926620">"Telefon"</string>
- <string name="permgroupdesc_phone" msgid="270048070781478204">"upućuje telefonske pozive i upravlja njima"</string>
- <string name="permgrouplab_sensors" msgid="9134046949784064495">"Senzori za telo"</string>
- <string name="permgroupdesc_sensors" msgid="2610631290633747752">"pristupa podacima senzora o vitalnim funkcijama"</string>
- <string name="permgrouplab_notifications" msgid="5472972361980668884">"Obaveštenja"</string>
- <string name="permgroupdesc_notifications" msgid="4608679556801506580">"prikazivanje obaveštenja"</string>
- <string name="capability_title_canRetrieveWindowContent" msgid="7554282892101587296">"da preuzima sadržaj prozora"</string>
- <string name="capability_desc_canRetrieveWindowContent" msgid="6195610527625237661">"Proverava sadržaj prozora sa kojim ostvarujete interakciju."</string>
- <string name="capability_title_canRequestTouchExploration" msgid="327598364696316213">"da uključi Istraživanja dodirom"</string>
- <string name="capability_desc_canRequestTouchExploration" msgid="4394677060796752976">"Stavke koje dodirnete će biti izgovorene naglas, a možete da se krećete po ekranu pokretima."</string>
- <string name="capability_title_canRequestFilterKeyEvents" msgid="2772371671541753254">"da prati tekst koji unosite"</string>
- <string name="capability_desc_canRequestFilterKeyEvents" msgid="2381315802405773092">"Obuhvata lične podatke kao što su brojevi kreditnih kartica i lozinke."</string>
- <string name="capability_title_canControlMagnification" msgid="7701572187333415795">"da upravlja uvećanjem prikaza"</string>
- <string name="capability_desc_canControlMagnification" msgid="2206586716709254805">"Upravlja nivoom zumiranja prikaza i određivanjem položaja."</string>
- <string name="capability_title_canPerformGestures" msgid="9106545062106728987">"Obavljanje pokreta"</string>
- <string name="capability_desc_canPerformGestures" msgid="6619457251067929726">"Može da dodiruje, lista, skuplja prikaz i obavlja druge pokrete."</string>
- <string name="capability_title_canCaptureFingerprintGestures" msgid="1189053104594608091">"Pokreti za otisak prsta"</string>
- <string name="capability_desc_canCaptureFingerprintGestures" msgid="6861869337457461274">"Može da registruje pokrete na senzoru za otisak prsta na uređaju."</string>
- <string name="capability_title_canTakeScreenshot" msgid="3895812893130071930">"Napravi snimak ekrana"</string>
- <string name="capability_desc_canTakeScreenshot" msgid="7762297374317934052">"Može da napravi snimak ekrana."</string>
- <string name="permlab_statusBar" msgid="8798267849526214017">"onemogućavanje ili izmena statusne trake"</string>
- <string name="permdesc_statusBar" msgid="5809162768651019642">"Dozvoljava aplikaciji da onemogući statusnu traku ili da dodaje i uklanja sistemske ikone."</string>
- <string name="permlab_statusBarService" msgid="2523421018081437981">"funkcionisanje kao statusna traka"</string>
- <string name="permdesc_statusBarService" msgid="6652917399085712557">"Dozvoljava aplikaciji da funkcioniše kao statusna traka."</string>
- <string name="permlab_expandStatusBar" msgid="1184232794782141698">"proširenje/skupljanje statusne trake"</string>
- <string name="permdesc_expandStatusBar" msgid="7180756900448498536">"Dozvoljava aplikaciji da proširuje ili skuplja statusnu traku."</string>
- <string name="permlab_fullScreenIntent" msgid="4310888199502509104">"prikazuje obaveštenja kao aktivnosti preko celog ekrana na zaključanom uređaju"</string>
- <string name="permdesc_fullScreenIntent" msgid="1100721419406643997">"Omogućava aplikaciji da na zaključanom uređaju prikazuje obaveštenja kao aktivnosti preko celog ekrana."</string>
- <string name="permlab_install_shortcut" msgid="7451554307502256221">"Instaliranje prečica"</string>
- <string name="permdesc_install_shortcut" msgid="4476328467240212503">"da dodaju prečice na početni ekran bez intervencije korisnika."</string>
- <string name="permlab_uninstall_shortcut" msgid="295263654781900390">"deinstaliranje prečica"</string>
- <string name="permdesc_uninstall_shortcut" msgid="1924735350988629188">"Omogućava aplikaciji da uklanja prečice sa početnog ekrana bez intervencije korisnika."</string>
- <string name="permlab_processOutgoingCalls" msgid="4075056020714266558">"preusmeravanje odlaznih poziva"</string>
- <string name="permdesc_processOutgoingCalls" msgid="7833149750590606334">"Dozvoljava aplikaciji da vidi koji broj se bira pri odlaznom pozivu uz opciju da preusmeri poziv na drugi broj ili ga potpuno prekine."</string>
- <string name="permlab_answerPhoneCalls" msgid="4131324833663725855">"odgovaraj na telefonske pozive"</string>
- <string name="permdesc_answerPhoneCalls" msgid="894386681983116838">"Dozvoljava aplikaciji da odgovori na dolazni telefonski poziv."</string>
- <string name="permlab_receiveSms" msgid="505961632050451881">"prijem tekstualnih poruka (SMS)"</string>
- <string name="permdesc_receiveSms" msgid="1797345626687832285">"Dozvoljava aplikaciji da prima i obrađuje SMS poruke. To znači da aplikacija može da nadgleda ili briše poruke koje se šalju uređaju, a da vam ih ne prikaže."</string>
- <string name="permlab_receiveMms" msgid="4000650116674380275">"prijem tekstualnih poruka (MMS)"</string>
- <string name="permdesc_receiveMms" msgid="958102423732219710">"Dozvoljava aplikaciji da prima i obrađuje MMS poruke. To znači da aplikacija može da nadgleda ili briše poruke koje se šalju uređaju, a da vam ih ne prikaže."</string>
- <string name="permlab_bindCellBroadcastService" msgid="586746677002040651">"Prosleđivanje poruka za mobilne uređaje na lokalitetu"</string>
- <string name="permdesc_bindCellBroadcastService" msgid="6540910200973641606">"Dozvoljava aplikaciji da se vezuje za modul poruka za mobilne uređaje na lokalitetu da bi prosleđivala poruke za mobilne uređaje na lokalitetu onako kako su primljene. Obaveštenja poruka za mobilne uređaje na lokalitetu se na nekim lokacijama primaju kao upozorenja na hitne slučajeve. Zlonamerne aplikacije mogu da utiču na performanse ili ometaju rad uređaja kada se primi poruka o hitnom slučaju za mobilne uređaje na lokalitetu."</string>
- <string name="permlab_manageOngoingCalls" msgid="281244770664231782">"Upravljanje odlaznim pozivima"</string>
- <string name="permdesc_manageOngoingCalls" msgid="7003138133829915265">"Omogućava aplikaciji da vidi detalje o odlaznim pozivima na uređaju i da kontroliše te pozive."</string>
- <string name="permlab_readCellBroadcasts" msgid="5869884450872137693">"čitanje poruka info servisa"</string>
- <string name="permdesc_readCellBroadcasts" msgid="672513437331980168">"Omogućava aplikaciji da čita poruke info servisa koje uređaj prima. Upozorenja info servisa se na nekim lokacijama primaju kao upozorenja na hitne slučajeve. Zlonamerne aplikacije mogu da utiču na performanse ili ometaju funkcionisanje uređaja kada se primi poruka info servisa o hitnom slučaju."</string>
- <string name="permlab_subscribedFeedsRead" msgid="217624769238425461">"čitanje prijavljenih fidova"</string>
- <string name="permdesc_subscribedFeedsRead" msgid="6911349196661811865">"Dozvoljava aplikaciji da preuzima detalje o trenutno sinhronizovanim fidovima."</string>
- <string name="permlab_sendSms" msgid="7757368721742014252">"šalje i pregleda SMS poruke"</string>
- <string name="permdesc_sendSms" msgid="6757089798435130769">"Dozvoljava aplikaciji da šalje SMS poruke. Ovo može da dovede do neočekivanih troškova. Zlonamerne aplikacije mogu da šalju poruke bez vaše potvrde, što može da izazove troškove."</string>
- <string name="permlab_readSms" msgid="5164176626258800297">"čitanje tekstualnih poruka (SMS ili MMS)"</string>
- <string name="permdesc_readSms" product="tablet" msgid="7912990447198112829">"Ova aplikacija može da čita sve SMS (tekstualne) poruke koje se čuvaju na tabletu."</string>
- <string name="permdesc_readSms" product="tv" msgid="3054753345758011986">"Ova aplikacija može da čita sve SMS (tekstualne) poruke koje se čuvaju na Android TV uređaju."</string>
- <string name="permdesc_readSms" product="default" msgid="774753371111699782">"Ova aplikacija može da čita sve SMS (tekstualne) poruke koje se čuvaju na telefonu."</string>
- <string name="permlab_receiveWapPush" msgid="4223747702856929056">"prijem tekstualnih poruka (WAP)"</string>
- <string name="permdesc_receiveWapPush" msgid="1638677888301778457">"Dozvoljava aplikaciji da prima i obrađuje WAP poruke. Ova dozvola uključuje mogućnost praćenja ili brisanja poruka koje vam se šalju, a koje vam se ne prikazuju."</string>
- <string name="permlab_getTasks" msgid="7460048811831750262">"preuzimanje pokrenutih aplikacija"</string>
- <string name="permdesc_getTasks" msgid="7388138607018233726">"Dozvoljava aplikaciji da preuzima informacije o aktuelnim i nedavno pokrenutim zadacima. Ovo može da omogući aplikaciji da otkrije informacije o tome koje se aplikacije koriste na uređaju."</string>
- <string name="permlab_manageProfileAndDeviceOwners" msgid="639849495253987493">"upravljanje vlasnicima profila i uređaja"</string>
- <string name="permdesc_manageProfileAndDeviceOwners" msgid="7304240671781989283">"Dozvoljava aplikaciji da podesi vlasnike profila i vlasnika uređaja."</string>
- <string name="permlab_reorderTasks" msgid="7598562301992923804">"promena redosleda pokrenutih aplikacija"</string>
- <string name="permdesc_reorderTasks" msgid="8796089937352344183">"Dozvoljava aplikaciji da premešta zadatke u prvi plan i u pozadinu. Aplikacija može da radi ovo bez vašeg unosa."</string>
- <string name="permlab_enableCarMode" msgid="893019409519325311">"omogućavanje režima rada u automobilu"</string>
- <string name="permdesc_enableCarMode" msgid="56419168820473508">"Dozvoljava aplikaciji da omogući režim rada u automobilu."</string>
- <string name="permlab_killBackgroundProcesses" msgid="6559320515561928348">"zatvaranje drugih aplikacija"</string>
- <string name="permdesc_killBackgroundProcesses" msgid="2357013583055434685">"Dozvoljava aplikaciji da zaustavi pozadinske procese drugih aplikacija. Ovo može da zaustavi druge aplikacije."</string>
- <string name="permlab_systemAlertWindow" msgid="5757218350944719065">"Ova aplikacija može da se prikazuje preko drugih aplikacija"</string>
- <string name="permdesc_systemAlertWindow" msgid="1145660714855738308">"Ova aplikacija može da se prikazuje preko drugih aplikacija ili drugih delova delova ekrana. To može da ometa standardno korišćenje aplikacija i način na koji se druge aplikacije prikazuju."</string>
- <string name="permlab_runInBackground" msgid="541863968571682785">"pokretanje u pozadini"</string>
- <string name="permdesc_runInBackground" msgid="4344539472115495141">"Ova aplikacija može da se pokreće u pozadini. To može brže da istroši bateriju."</string>
- <string name="permlab_useDataInBackground" msgid="783415807623038947">"korišćenje podataka u pozadini"</string>
- <string name="permdesc_useDataInBackground" msgid="1230753883865891987">"Ova aplikacija može da koristi podatke u pozadini. To može da poveća potrošnju podataka."</string>
- <string name="permlab_schedule_exact_alarm" msgid="6683283918033029730">"Zakazivanje vremenski preciznih radnji"</string>
- <string name="permdesc_schedule_exact_alarm" msgid="8198009212013211497">"Ova aplikacija može da zakaže da se rad dogodi u željeno vreme u budućnosti. To znači i da aplikacija može da radi kada ne koristite aktivno uređaj."</string>
- <string name="permlab_use_exact_alarm" msgid="348045139777131552">"Zakazivanje alarma ili podsetnika za događaje"</string>
- <string name="permdesc_use_exact_alarm" msgid="7033761461886938912">"Ova aplikacija može da zakazuje radnje poput alarma i podsetnika da bi vas obaveštavala u željeno vreme u budućnosti."</string>
- <string name="permlab_persistentActivity" msgid="464970041740567970">"omogućavanje neprekidne aktivnosti aplikacije"</string>
- <string name="permdesc_persistentActivity" product="tablet" msgid="6055271149187369916">"Dozvoljava aplikaciji da učini sopstvene komponente trajnim u memoriji. Ovo može da ograniči memoriju dostupnu drugim aplikacijama i uspori tablet."</string>
- <string name="permdesc_persistentActivity" product="tv" msgid="6800526387664131321">"Dozvoljava aplikaciji da trajno zadrži neke svoje delove u memoriji. Ovo može da ograniči memoriju dostupnu drugim aplikacijama i uspori Android TV uređaj."</string>
- <string name="permdesc_persistentActivity" product="default" msgid="1914841924366562051">"Dozvoljava aplikaciji da učini sopstvene komponente trajnim u memoriji. Ovo može da ograniči memoriju dostupnu drugim aplikacijama i uspori telefon."</string>
- <string name="permlab_foregroundService" msgid="1768855976818467491">"pokreni uslugu u prvom planu"</string>
- <string name="permdesc_foregroundService" msgid="8720071450020922795">"Dozvoljava aplikaciji da koristi usluge u prvom planu."</string>
- <string name="permlab_foregroundServiceCamera" msgid="7814751737955715297">"pokretanje usluge u prvom planu koja pripada tipu „camera“"</string>
- <string name="permdesc_foregroundServiceCamera" msgid="6973701931250595727">"Dozvoljava aplikaciji da koristi usluge u prvom planu koje pripadaju tipu „camera“"</string>
- <string name="permlab_foregroundServiceConnectedDevice" msgid="3019650546176872501">"pokretanje usluge u prvom planu koja pripada tipu „connectedDevice“"</string>
- <string name="permdesc_foregroundServiceConnectedDevice" msgid="1067457315741352963">"Dozvoljava aplikaciji da koristi usluge u prvom planu koje pripadaju tipu „connectedDevice“"</string>
- <string name="permlab_foregroundServiceDataSync" msgid="5847463514326881076">"pokretanje usluge u prvom planu koja pripada tipu „dataSync“"</string>
- <string name="permdesc_foregroundServiceDataSync" msgid="2267140263423973050">"Dozvoljava aplikaciji da koristi usluge u prvom planu koje pripadaju tipu „dataSync“"</string>
- <string name="permlab_foregroundServiceLocation" msgid="3745428302378535690">"pokretanje usluge u prvom planu koja pripada tipu „location“"</string>
- <string name="permdesc_foregroundServiceLocation" msgid="118894034365177183">"Dozvoljava aplikaciji da koristi usluge u prvom planu koje pripadaju tipu „location“"</string>
- <string name="permlab_foregroundServiceMediaPlayback" msgid="4002687983891935514">"pokretanje usluge u prvom planu koja pripada tipu „mediaPlayback“"</string>
- <string name="permdesc_foregroundServiceMediaPlayback" msgid="3638032446063968043">"Dozvoljava aplikaciji da koristi usluge u prvom planu koje pripadaju tipu „mediaPlayback“"</string>
- <string name="permlab_foregroundServiceMediaProjection" msgid="2630868915733312527">"pokretanje usluge u prvom planu koja pripada tipu „mediaProjection“"</string>
- <string name="permdesc_foregroundServiceMediaProjection" msgid="4805677128082002298">"Dozvoljava aplikaciji da koristi usluge u prvom planu koje pripadaju tipu „mediaProjection“"</string>
- <string name="permlab_foregroundServiceMicrophone" msgid="7390033424890545399">"pokretanje usluge u prvom planu koja pripada tipu „microphone“"</string>
- <string name="permdesc_foregroundServiceMicrophone" msgid="1206041516173483201">"Dozvoljava aplikaciji da koristi usluge u prvom planu koje pripadaju tipu „microphone“"</string>
- <string name="permlab_foregroundServicePhoneCall" msgid="627937743867697892">"pokretanje usluge u prvom planu koja pripada tipu „phoneCall“"</string>
- <string name="permdesc_foregroundServicePhoneCall" msgid="5941660252587015147">"Dozvoljava aplikaciji da koristi usluge u prvom planu koje pripadaju tipu „phoneCall“"</string>
- <string name="permlab_foregroundServiceHealth" msgid="3675776442080928184">"pokretanje usluge u prvom planu koja pripada tipu „health“"</string>
- <string name="permdesc_foregroundServiceHealth" msgid="2024586220562667185">"Dozvoljava aplikaciji da koristi usluge u prvom planu koje pripadaju tipu „health“"</string>
- <string name="permlab_foregroundServiceRemoteMessaging" msgid="105670277002780950">"pokretanje usluge u prvom planu koja pripada tipu „remoteMessaging“"</string>
- <string name="permdesc_foregroundServiceRemoteMessaging" msgid="8767598075877576277">"Dozvoljava aplikaciji da koristi usluge u prvom planu koje pripadaju tipu „remoteMessaging“"</string>
- <string name="permlab_foregroundServiceSystemExempted" msgid="1597663713590612685">"pokretanje usluge u prvom planu koja pripada tipu „systemExempted“"</string>
- <string name="permdesc_foregroundServiceSystemExempted" msgid="947381760834649622">"Dozvoljava aplikaciji da koristi usluge u prvom planu koje pripadaju tipu „systemExempted“"</string>
- <string name="permlab_foregroundServiceFileManagement" msgid="2585000987966045030">"pokretanje usluge u prvom planu koja pripada tipu „fileManagement“"</string>
- <string name="permdesc_foregroundServiceFileManagement" msgid="417103601269698508">"Dozvoljava aplikaciji da koristi usluge u prvom planu koje pripadaju tipu „fileManagement“"</string>
- <string name="permlab_foregroundServiceSpecialUse" msgid="7973536745876645082">"pokretanje usluge u prvom planu koja pripada tipu „specialUse“"</string>
- <string name="permdesc_foregroundServiceSpecialUse" msgid="646713654541885919">"Dozvoljava aplikaciji da koristi usluge u prvom planu koje pripadaju tipu „specialUse“"</string>
- <string name="permlab_getPackageSize" msgid="375391550792886641">"merenje memorijskog prostora u aplikaciji"</string>
- <string name="permdesc_getPackageSize" msgid="742743530909966782">"Dozvoljava aplikaciji da preuzme veličine kôda, podataka i keša."</string>
- <string name="permlab_writeSettings" msgid="8057285063719277394">"izmena podešavanja sistema"</string>
- <string name="permdesc_writeSettings" msgid="8293047411196067188">"Dozvoljava aplikaciji da menja podatke o podešavanju sistema. Zlonamerne aplikacije mogu da oštete konfiguraciju sistema."</string>
- <string name="permlab_receiveBootCompleted" msgid="6643339400247325379">"pokretanje pri pokretanju sistema"</string>
- <string name="permdesc_receiveBootCompleted" product="tablet" msgid="5565659082718177484">"Omogućava da se aplikacija pokrene odmah nakon pokretanja sistema. To može da uspori pokretanje tableta, pri čemu ova aplikacija može da uspori funkcionisanje celog tableta time što će uvek biti aktivna."</string>
- <string name="permdesc_receiveBootCompleted" product="tv" msgid="4900842256047614307">"Dozvoljava aplikaciji da se pokrene odmah po uključivanju sistema. To može da uspori pokretanje Android TV uređaja i aplikacija može da uspori funkcionisanje uređaja u celini tako što će uvek biti aktivna."</string>
- <string name="permdesc_receiveBootCompleted" product="default" msgid="7912677044558690092">"Omogućava da se aplikacija pokrene čim se sistem pokrene. To može da uspori pokretanje telefona, pri čemu ova aplikacija može da uspori funkcionisanje celog telefona time što će uvek biti aktivna."</string>
- <string name="permlab_broadcastSticky" msgid="4552241916400572230">"slanje prijemčivih emitovanja"</string>
- <string name="permdesc_broadcastSticky" product="tablet" msgid="5058486069846384013">"Dozvoljava aplikaciji da šalje prijemčiva emitovanja, koja ostaju po završetku emitovanja. Prekomerna upotreba može da uspori ili destabilizuje tablet tako što će ga primorati da troši previše memorije."</string>
- <string name="permdesc_broadcastSticky" product="tv" msgid="2338185920171000650">"Dozvoljava aplikaciji da šalje lepljiva emitovanja koja ostaju po završetku emitovanja. Prekomerna upotreba može da uspori ili destabilizuje Android TV uređaj tako što će ga primorati da troši previše memorije."</string>
- <string name="permdesc_broadcastSticky" product="default" msgid="134529339678913453">"Dozvoljava aplikaciji da šalje prijemčiva emitovanja, koja ostaju po završetku emitovanja. Prekomerna upotreba može da uspori ili destabilizuje telefon tako što će ga primorati da troši previše memorije."</string>
- <string name="permlab_readContacts" msgid="8776395111787429099">"čitanje kontakata"</string>
- <string name="permdesc_readContacts" product="tablet" msgid="6430093481659992692">"Dozvoljava aplikaciji da čita podatke o kontaktima koje čuvate na tabletu. Aplikacije će imati pristup i nalozima na vašem tabletu na kojima su napravljeni kontakti. Tu mogu da spadaju nalozi koje su otvorile aplikacije koje ste instalirali. Ova dozvola omogućava aplikacijama da čuvaju podatke o kontaktima i zlonamerne aplikacije mogu da dele podatke o kontaktima bez vašeg znanja."</string>
- <string name="permdesc_readContacts" product="tv" msgid="8400138591135554789">"Dozvoljava aplikaciji da čita podatke o kontaktima koje čuvate na Android TV uređaju. Aplikacije će imati pristup i nalozima na vašem Android TV uređaju na kojima su napravljeni kontakti. Tu mogu da spadaju nalozi koje su otvorile aplikacije koje ste instalirali. Ova dozvola omogućava aplikacijama da čuvaju podatke o kontaktima i zlonamerne aplikacije mogu da dele podatke o kontaktima bez vašeg znanja."</string>
- <string name="permdesc_readContacts" product="default" msgid="4911989776203207644">"Dozvoljava aplikaciji da čita podatke o kontaktima koje čuvate na telefonu. Aplikacije će imati pristup i nalozima na vašem telefonu na kojima su napravljeni kontakti. Tu mogu da spadaju nalozi koje su otvorile aplikacije koje ste instalirali. Ova dozvola omogućava aplikacijama da čuvaju podatke o kontaktima i zlonamerne aplikacije mogu da dele podatke o kontaktima bez vašeg znanja."</string>
- <string name="permlab_writeContacts" msgid="8919430536404830430">"izmena kontakata"</string>
- <string name="permdesc_writeContacts" product="tablet" msgid="6422419281427826181">"Dozvoljava aplikaciji da menja podatke o kontaktima koje čuvate na tabletu. Ova dozvola omogućava aplikacijama da brišu podatke o kontaktima."</string>
- <string name="permdesc_writeContacts" product="tv" msgid="6488872735379978935">"Dozvoljava aplikaciji da menja podatke o kontaktima koje čuvate na Android TV uređaju. Ova dozvola omogućava aplikacijama da brišu podatke o kontaktima."</string>
- <string name="permdesc_writeContacts" product="default" msgid="8304795696237065281">"Dozvoljava aplikaciji da menja podatke o kontaktima koje čuvate na telefonu. Ova dozvola omogućava aplikacijama da brišu podatke o kontaktima."</string>
- <string name="permlab_readCallLog" msgid="1739990210293505948">"čitanje evidencije poziva"</string>
- <string name="permdesc_readCallLog" msgid="8964770895425873433">"Ova aplikacija može da čita istoriju poziva."</string>
- <string name="permlab_writeCallLog" msgid="670292975137658895">"pisanje evidencije poziva"</string>
- <string name="permdesc_writeCallLog" product="tablet" msgid="2657525794731690397">"Dozvoljava aplikaciji da menja evidenciju poziva na tabletu, uključujući podatke o dolaznim i odlaznim pozivima. Zlonamerne aplikacije mogu ovo da koriste da bi brisale ili menjale evidenciju poziva."</string>
- <string name="permdesc_writeCallLog" product="tv" msgid="3934939195095317432">"Dozvoljava aplikaciji da menja evidenciju poziva na Android TV uređaju, uključujući podatke o dolaznim i odlaznim pozivima. Zlonamerne aplikacije mogu ovo da koriste za brisanje ili menjanje evidencije poziva."</string>
- <string name="permdesc_writeCallLog" product="default" msgid="5903033505665134802">"Dozvoljava aplikaciji da menja evidenciju poziva na telefonu, uključujući podatke o dolaznim i odlaznim pozivima. Zlonamerne aplikacije mogu ovo da koriste da bi brisale ili menjale evidenciju poziva."</string>
- <string name="permlab_bodySensors" msgid="662918578601619569">"Pristup podacima senzora za telo, kao što je puls, u toku korišćenja"</string>
- <string name="permdesc_bodySensors" product="default" msgid="7652650410295512140">"Dozvoljava aplikaciji da pristupa podacima senzora za telo, kao što su puls, temperatura i procenat kiseonika u krvi dok se aplikacija koristi."</string>
- <string name="permlab_bodySensors_background" msgid="4912560779957760446">"Pristup podacima senzora za telo, kao što je puls, u pozadini"</string>
- <string name="permdesc_bodySensors_background" product="default" msgid="8870726027557749417">"Dozvoljava aplikaciji da pristupa podacima senzora za telo, kao što su puls, temperatura i procenat kiseonika u krvi dok je aplikacija u pozadini."</string>
- <string name="permlab_readCalendar" msgid="6408654259475396200">"Čitanje događaja i podataka iz kalendara"</string>
- <string name="permdesc_readCalendar" product="tablet" msgid="515452384059803326">"Ova aplikacija može da čita sve događaje iz kalendara koje čuvate na tabletu, kao i da deli ili čuva podatke iz kalendara."</string>
- <string name="permdesc_readCalendar" product="tv" msgid="5811726712981647628">"Ova aplikacija može da čita sve događaje iz kalendara koje čuvate na Android TV uređaju, kao i da deli ili čuva podatke iz kalendara."</string>
- <string name="permdesc_readCalendar" product="default" msgid="9118823807655829957">"Ova aplikacija može da čita sve događaje iz kalendara koje čuvate na telefonu, kao i da deli ili čuva podatke iz kalendara."</string>
- <string name="permlab_writeCalendar" msgid="6422137308329578076">"dodavanje ili izmena kalendarskih događaja i slanje poruka e-pošte gostima bez znanja vlasnika"</string>
- <string name="permdesc_writeCalendar" product="tablet" msgid="8722230940717092850">"Ova aplikaciji može da dodaje, uklanja ili menja događaje iz kalendara na tabletu. Ova aplikacija može da šalje poruke koje izgledaju kao da ih šalju vlasnici kalendara ili da menja događaje bez znanja vlasnika."</string>
- <string name="permdesc_writeCalendar" product="tv" msgid="951246749004952706">"Ova aplikacija može da dodaje, uklanja ili menja događaje iz kalendara na Android TV uređaju. Ova aplikacija može da šalje poruke koje izgledaju kao da ih šalju vlasnici kalendara ili da menja događaje bez znanja vlasnika."</string>
- <string name="permdesc_writeCalendar" product="default" msgid="5416380074475634233">"Ova aplikaciji može da dodaje, uklanja ili menja događaje iz kalendara na telefonu. Ova aplikacija može da šalje poruke koje izgledaju kao da ih šalju vlasnici kalendara ili da menja događaje bez znanja vlasnika."</string>
- <string name="permlab_accessLocationExtraCommands" msgid="5162339812057983988">"pristup dodatnim komandama dobavljača lokacije"</string>
- <string name="permdesc_accessLocationExtraCommands" msgid="355369611979907967">"Omogućava aplikaciji da pristupa dodatnim komandama davaoca usluga lokacije. To može da omogući aplikaciji da utiče na rad GPS-a ili drugih izvora lokacije."</string>
- <string name="permlab_accessFineLocation" msgid="6426318438195622966">"pristup preciznoj lokaciji samo u prvom planu"</string>
- <string name="permdesc_accessFineLocation" msgid="6732174080240016335">"Ova aplikacija može da odredi vašu tačnu lokaciju na osnovu usluga lokacije dok se aplikacija koristi. Usluge lokacije za uređaj moraju da budu uključene da bi aplikacija odredila lokaciju. To može da poveća potrošnju baterije."</string>
- <string name="permlab_accessCoarseLocation" msgid="1561042925407799741">"pristup približnoj lokaciji samo u prvom planu"</string>
- <string name="permdesc_accessCoarseLocation" msgid="778521847873199160">"Ova aplikacija može da odredi vašu približnu lokaciju na osnovu usluga lokacije dok se aplikacija koristi. Usluge lokacije za uređaj moraju da budu uključene da bi aplikacija odredila lokaciju."</string>
- <string name="permlab_accessBackgroundLocation" msgid="1721164702777366138">"pristup lokaciji u pozadini"</string>
- <string name="permdesc_accessBackgroundLocation" msgid="8264885066095638105">"Ova aplikacija može da pristupa lokaciji u bilo kom trenutku, čak i dok se aplikacija ne koristi."</string>
- <string name="permlab_modifyAudioSettings" msgid="6129039778010031815">"promena audio podešavanja"</string>
- <string name="permdesc_modifyAudioSettings" msgid="8687227609663124921">"Dozvoljava aplikaciji da menja globalna audio podešavanja kao što su jačina zvuka i izbor zvučnika koji se koristi kao izlaz."</string>
- <string name="permlab_recordAudio" msgid="1208457423054219147">"snimanje audio zapisa"</string>
- <string name="permdesc_recordAudio" msgid="5857246765327514062">"Ova aplikacija može da snima zvuk pomoću mikrofona dok se aplikacija koristi."</string>
- <string name="permlab_recordBackgroundAudio" msgid="5891032812308878254">"da snima zvuk u pozadini"</string>
- <string name="permdesc_recordBackgroundAudio" msgid="1992623135737407516">"Ova aplikacija može da snima zvuk pomoću mikrofona u bilo kom trenutku."</string>
- <string name="permlab_detectScreenCapture" msgid="4447042362828799433">"otkrivanje snimanja ekrana u prozorima aplikacija"</string>
- <string name="permdesc_detectScreenCapture" msgid="3485784917960342284">"Ako se tokom korišćenja ove aplikacije napravi snimak ekrana, aplikacija će dobiti obaveštenje."</string>
- <string name="permlab_sim_communication" msgid="176788115994050692">"slanje komandi na SIM"</string>
- <string name="permdesc_sim_communication" msgid="4179799296415957960">"Omogućava aplikaciji da šalje komande SIM kartici. To je veoma opasno."</string>
- <string name="permlab_activityRecognition" msgid="1782303296053990884">"prepoznavanje fizičkih aktivnosti"</string>
- <string name="permdesc_activityRecognition" msgid="8667484762991357519">"Ova aplikacija može da prepozna fizičke aktivnosti."</string>
- <string name="permlab_camera" msgid="6320282492904119413">"snimanje fotografija i video snimaka"</string>
- <string name="permdesc_camera" msgid="5240801376168647151">"Ova aplikacija može da snima slike i video snimke pomoću kamere dok se aplikacija koristi."</string>
- <string name="permlab_backgroundCamera" msgid="7549917926079731681">"da snima slike i video snimke u pozadini"</string>
- <string name="permdesc_backgroundCamera" msgid="1615291686191138250">"Ova aplikacija može da snima fotografije i video snimke pomoću kamere u bilo kom trenutku."</string>
- <string name="permlab_systemCamera" msgid="3642917457796210580">"Dozvolite nekoj aplikaciji ili usluzi da pristupa kamerama sistema da bi snimala slike i video snimke"</string>
- <string name="permdesc_systemCamera" msgid="5938360914419175986">"Ova privilegovana sistemska aplikacija može da snima slike i video snimke pomoću kamere sistema u bilo kom trenutku. Aplikacija treba da ima i dozvolu android.permission.CAMERA"</string>
- <string name="permlab_cameraOpenCloseListener" msgid="5548732769068109315">"Dozvolite aplikaciji ili usluzi da dobija povratne pozive o otvaranju ili zatvaranju uređaja sa kamerom."</string>
- <string name="permdesc_cameraOpenCloseListener" msgid="2002636131008772908">"Ova aplikacija može da dobija povratne pozive kada se bilo koji uređaj sa kamerom otvara ili zatvara (pomoću neke aplikacije)."</string>
- <string name="permlab_vibrate" msgid="8596800035791962017">"kontrola vibracije"</string>
- <string name="permdesc_vibrate" msgid="8733343234582083721">"Dozvoljava aplikaciji da kontroliše vibraciju."</string>
- <string name="permdesc_vibrator_state" msgid="7050024956594170724">"Dozvoljava aplikaciji da pristupa stanju vibriranja."</string>
- <string name="permlab_callPhone" msgid="1798582257194643320">"direktno pozivanje brojeva telefona"</string>
- <string name="permdesc_callPhone" msgid="5439809516131609109">"Dozvoljava aplikaciji da poziva brojeve telefona bez vaše dozvole. Ovo može da dovede do neočekivanih troškova ili poziva. Imajte na umu da ovo ne dozvoljava aplikaciji da poziva brojeve za hitne slučajeve. Zlonamerne aplikacije mogu da pozivaju bez vaše potvrde, što može da dovede do troškova."</string>
- <string name="permlab_accessImsCallService" msgid="442192920714863782">"pristup usluzi poziva pomoću razmene trenutnih poruka"</string>
- <string name="permdesc_accessImsCallService" msgid="6328551241649687162">"Dozvoljava aplikaciji da koristi uslugu razmene trenutnih poruka da bi upućivala pozive bez vaše intervencije."</string>
- <string name="permlab_readPhoneState" msgid="8138526903259297969">"čitanje statusa i identiteta telefona"</string>
- <string name="permdesc_readPhoneState" msgid="7229063553502788058">"Dozvoljava aplikaciji da pristupa funkcijama telefona na uređaju. Ova dozvola omogućava aplikaciji da utvrdi broj telefona i ID-ove uređaja, zatim da li je poziv aktivan, kao i broj daljinskog uređaja sa kojim je uspostavljen poziv."</string>
- <string name="permlab_readBasicPhoneState" msgid="3214853233263871347">"očitavanje osnovnog telefonskog statusa i identiteta"</string>
- <string name="permdesc_readBasicPhoneState" msgid="828185691675460520">"Omogućava aplikaciji da pristupa osnovnim telefonskim funkcijama uređaja."</string>
- <string name="permlab_manageOwnCalls" msgid="9033349060307561370">"preusmeravanje poziva preko sistema"</string>
- <string name="permdesc_manageOwnCalls" msgid="4431178362202142574">"Dozvoljava aplikaciji da preusmerava pozive preko sistema da bi poboljšala doživljaj pozivanja."</string>
- <string name="permlab_callCompanionApp" msgid="3654373653014126884">"pregled i kontrola poziva preko sistema."</string>
- <string name="permdesc_callCompanionApp" msgid="8474168926184156261">"Dozvoljava aplikaciji da pregleda i kontroliše trenutne pozive na uređaju. To obuhvata informacije poput brojeva telefona i statusa poziva."</string>
- <string name="permlab_exemptFromAudioRecordRestrictions" msgid="1164725468350759486">"izuzimanje iz ograničenja za snimanje zvuka"</string>
- <string name="permdesc_exemptFromAudioRecordRestrictions" msgid="2425117015896871976">"Izuzmite aplikaciju iz ograničenja za snimanje zvuka."</string>
- <string name="permlab_acceptHandover" msgid="2925523073573116523">"nastavi poziv u drugoj aplikaciji"</string>
- <string name="permdesc_acceptHandovers" msgid="7129026180128626870">"Dozvoljava aplikaciji da nastavi poziv koji je započet u drugoj aplikaciji."</string>
- <string name="permlab_readPhoneNumbers" msgid="5668704794723365628">"čitanje brojeva telefona"</string>
- <string name="permdesc_readPhoneNumbers" msgid="7368652482818338871">"Dozvoljava aplikaciji da pristupa brojevima telefona na uređaju."</string>
- <string name="permlab_wakeLock" product="automotive" msgid="1904736682319375676">"ne isključuj ekran u automobilu"</string>
- <string name="permlab_wakeLock" product="tablet" msgid="1527660973931694000">"sprečavanje prelaska tableta u stanje spavanja"</string>
- <string name="permlab_wakeLock" product="tv" msgid="2856941418123343518">"sprečava Android TV uređaj da pređe u stanje spavanja"</string>
- <string name="permlab_wakeLock" product="default" msgid="569409726861695115">"sprečavanje prelaska telefona u stanje spavanja"</string>
- <string name="permdesc_wakeLock" product="automotive" msgid="5995045369683254571">"Dozvoljava aplikaciji da ne isključuje ekran u automobilu."</string>
- <string name="permdesc_wakeLock" product="tablet" msgid="2441742939101526277">"Dozvoljava aplikaciji da spreči tablet da pređe u stanje spavanja."</string>
- <string name="permdesc_wakeLock" product="tv" msgid="2329298966735118796">"Dozvoljava aplikaciji da spreči Android TV uređaj da pređe u stanje spavanja."</string>
- <string name="permdesc_wakeLock" product="default" msgid="3689523792074007163">"Dozvoljava aplikaciji da spreči telefon da pređe u stanje spavanja."</string>
- <string name="permlab_transmitIr" msgid="8077196086358004010">"prenos infracrvenih zraka"</string>
- <string name="permdesc_transmitIr" product="tablet" msgid="5884738958581810253">"Dozvoljava aplikaciji da koristi odašiljač infracrvenih zraka tableta."</string>
- <string name="permdesc_transmitIr" product="tv" msgid="3278506969529173281">"Dozvoljava da aplikacija koristi odašiljač infracrvenih zraka na Android TV uređaju."</string>
- <string name="permdesc_transmitIr" product="default" msgid="8484193849295581808">"Dozvoljava aplikaciji da koristi odašiljač infracrvenih zraka telefona."</string>
- <string name="permlab_setWallpaper" msgid="6959514622698794511">"podešavanje pozadine"</string>
- <string name="permdesc_setWallpaper" msgid="2973996714129021397">"Dozvoljava aplikaciji da postavlja pozadinu sistema."</string>
- <string name="permlab_setWallpaperHints" msgid="1153485176642032714">"prilagođavanje veličine pozadine"</string>
- <string name="permdesc_setWallpaperHints" msgid="6257053376990044668">"Dozvoljava aplikaciji da podesi savete za sistemsku veličinu pozadine."</string>
- <string name="permlab_setTimeZone" msgid="7922618798611542432">"podešavanje vremenske zone"</string>
- <string name="permdesc_setTimeZone" product="tablet" msgid="1788868809638682503">"Dozvoljava aplikaciji da promeni vremensku zonu tableta."</string>
- <string name="permdesc_setTimeZone" product="tv" msgid="9069045914174455938">"Dozvoljava aplikaciji da menja vremensku zonu Android TV uređaja."</string>
- <string name="permdesc_setTimeZone" product="default" msgid="4611828585759488256">"Dozvoljava aplikaciji da promeni vremensku zonu telefona."</string>
- <string name="permlab_getAccounts" msgid="5304317160463582791">"pronalaženje naloga na uređaju"</string>
- <string name="permdesc_getAccounts" product="tablet" msgid="1784452755887604512">"Dozvoljava aplikaciji da preuzima listu naloga poznatih tabletu. Ovo može da obuhvata bilo koje naloge koje prave aplikacije koje instalirate."</string>
- <string name="permdesc_getAccounts" product="tv" msgid="437604680436540822">"Dozvoljava aplikaciji da dođe do liste naloga poznatih Android TV uređaju. Ovo može da obuhvata sve naloge koje otvaraju aplikacije koje ste instalirali."</string>
- <string name="permdesc_getAccounts" product="default" msgid="2491273043569751867">"Dozvoljava aplikaciji da preuzima listu naloga poznatih telefonu. Ovo može da obuhvata bilo koje naloge koje prave aplikacije koje instalirate."</string>
- <string name="permlab_accessNetworkState" msgid="2349126720783633918">"pregled mrežnih veza"</string>
- <string name="permdesc_accessNetworkState" msgid="4394564702881662849">"Dozvoljava aplikaciji da pregleda informacije o mrežnim vezama kao što su informacije o tome koje mreže postoje i koje mreže su povezane."</string>
- <string name="permlab_createNetworkSockets" msgid="3224420491603590541">"ima pun mrežni pristup"</string>
- <string name="permdesc_createNetworkSockets" msgid="7722020828749535988">"Dozvoljava aplikaciji da pravi mrežne priključke i koristi prilagođene mrežne protokole. Pregledač i druge aplikacije omogućavaju slanje podataka na Internet, pa ova dozvola nije potrebna za slanje podataka na Internet."</string>
- <string name="permlab_changeNetworkState" msgid="8945711637530425586">"promena veze sa mrežom"</string>
- <string name="permdesc_changeNetworkState" msgid="649341947816898736">"Dozvoljava aplikaciji da menja status povezivanja sa mrežom."</string>
- <string name="permlab_changeTetherState" msgid="9079611809931863861">"promena povezivanja privezivanjem"</string>
- <string name="permdesc_changeTetherState" msgid="3025129606422533085">"Dozvoljava aplikaciji da menja status veze sa privezanom mrežom."</string>
- <string name="permlab_accessWifiState" msgid="5552488500317911052">"pregled WiFi veza"</string>
- <string name="permdesc_accessWifiState" msgid="6913641669259483363">"Dozvoljava aplikaciji da pregleda informacije o WiFi umrežavanju, kao što su informacije o tome da li je WiFi omogućen i nazivi povezanih WiFi uređaja."</string>
- <string name="permlab_changeWifiState" msgid="7947824109713181554">"povezivanje i prekid veze sa WiFi mrežom"</string>
- <string name="permdesc_changeWifiState" msgid="7170350070554505384">"Dozvoljava aplikaciji da se povezuje sa pristupnim tačkama za WiFi i prekida vezu sa njima, kao i da unosi promene u konfiguraciju uređaja za WiFi mreže."</string>
- <string name="permlab_changeWifiMulticastState" msgid="285626875870754696">"omogućavanje prijema višesmernog WiFi saobraćaja"</string>
- <string name="permdesc_changeWifiMulticastState" product="tablet" msgid="191079868596433554">"Dozvoljava aplikaciji da prima pakete koji se šalju na sve uređaje na WiFi mreži pomoću višesmernih adresa, a ne samo na tablet. Koristi više napajanja od režima jednosmernog saobraćaja."</string>
- <string name="permdesc_changeWifiMulticastState" product="tv" msgid="1336952358450652595">"Dozvoljava aplikaciji da prima pakete koji se šalju na sve uređaje na WiFi mreži pomoću višesmernih adresa, a ne samo na Android TV uređaj. Koristi više energije od režima bez višesmernog slanja."</string>
- <string name="permdesc_changeWifiMulticastState" product="default" msgid="8296627590220222740">"Dozvoljava aplikaciji da prima pakete koji se šalju na sve uređaje na WiFi mreži pomoću višesmernih adresa, a ne samo na telefon. Koristi više napajanja od režima jednosmernog saobraćaja."</string>
- <string name="permlab_bluetoothAdmin" msgid="6490373569441946064">"pristup Bluetooth podešavanjima"</string>
- <string name="permdesc_bluetoothAdmin" product="tablet" msgid="5370837055438574863">"Dozvoljava aplikaciji da konfiguriše lokalni Bluetooth tablet, kao i da otkrije daljinske uređaje i upari se sa njima."</string>
- <string name="permdesc_bluetoothAdmin" product="tv" msgid="1623992984547014588">"Dozvoljava aplikaciji da konfiguriše Bluetooth na Android TV uređaju i da otkrije udaljene uređaje i upari se sa njima."</string>
- <string name="permdesc_bluetoothAdmin" product="default" msgid="7381341743021234863">"Dozvoljava aplikaciji da konfiguriše lokalni Bluetooth telefon, kao i da otkrije daljinske uređaje i upari se sa njima."</string>
- <string name="permlab_accessWimaxState" msgid="7029563339012437434">"povezivanje i prekid veze sa WiMAX-om"</string>
- <string name="permdesc_accessWimaxState" msgid="5372734776802067708">"Dozvoljava aplikaciji da utvrdi da li je WiMAX omogućen, kao i informacije o bilo kojim povezanim WiMAX mrežama."</string>
- <string name="permlab_changeWimaxState" msgid="6223305780806267462">"promeni WiMAX statusa"</string>
- <string name="permdesc_changeWimaxState" product="tablet" msgid="4011097664859480108">"Dozvoljava aplikaciji da povezuje tablet sa WiMAX mrežama i prekida veze sa njima."</string>
- <string name="permdesc_changeWimaxState" product="tv" msgid="5373274458799425276">"Dozvoljava aplikaciji da povezuje Android TV uređaj sa WiMAX mrežama i da prekida tu vezu."</string>
- <string name="permdesc_changeWimaxState" product="default" msgid="1551666203780202101">"Dozvoljava aplikaciji da povezuje telefon sa WiMAX mrežama i prekida veze sa njima."</string>
- <string name="permlab_bluetooth" msgid="586333280736937209">"uparivanje sa Bluetooth uređajima"</string>
- <string name="permdesc_bluetooth" product="tablet" msgid="3053222571491402635">"Dozvoljava aplikaciji da pregleda konfiguraciju Bluetooth-a na tabletu, kao i da uspostavlja i prihvata veze sa uparenim uređajima."</string>
- <string name="permdesc_bluetooth" product="tv" msgid="8851534496561034998">"Dozvoljava aplikaciji da pregleda konfiguraciju Bluetooth-a na Android TV uređaju i da uspostavlja i prihvata veze sa uparenim uređajima."</string>
- <string name="permdesc_bluetooth" product="default" msgid="2779606714091276746">"Dozvoljava aplikaciji da pregleda konfiguraciju Bluetooth-a na telefonu, kao i da uspostavlja i prihvata veze sa uparenim uređajima."</string>
- <string name="permlab_bluetooth_scan" msgid="5402587142833124594">"otkrivanje i uparivanje sa obližnjim Bluetooth uređ."</string>
- <string name="permdesc_bluetooth_scan" product="default" msgid="6540723536925289276">"Dozvoljava aplikaciji da otkriva Bluetooth uređaje u blizini i uparuje se sa njima"</string>
- <string name="permlab_bluetooth_connect" msgid="6657463246355003528">"povezivanje sa uparenim Bluetooth uređajima"</string>
- <string name="permdesc_bluetooth_connect" product="default" msgid="4546016548795544617">"Dozvoljava aplikaciji da se povezuje sa uparenim Bluetooth uređajima"</string>
- <string name="permlab_bluetooth_advertise" msgid="2781147747928853177">"oglašavanje na Bluetooth uređajima u blizini"</string>
- <string name="permdesc_bluetooth_advertise" product="default" msgid="6085174451034210183">"Dozvoljava aplikaciji da se oglašava na Bluetooth uređajima u blizini"</string>
- <string name="permlab_uwb_ranging" msgid="8141915781475770665">"određivanje razdaljine između uređaja ultra-širokog pojasa u blizini"</string>
- <string name="permdesc_uwb_ranging" msgid="2519723069604307055">"Dozvoljava aplikaciji da određuje relativnu razdaljinu između uređaja ultra-širokog pojasa u blizini"</string>
- <string name="permlab_nearby_wifi_devices" msgid="392774237063608500">"interakcija sa WiFi uređajima u blizini"</string>
- <string name="permdesc_nearby_wifi_devices" msgid="3054307728646332906">"Dozvoljava aplikaciji da se oglašava, povezuje i utvrđuje relativnu poziciju WiFi uređaja u blizini"</string>
- <string name="permlab_preferredPaymentInfo" msgid="5274423844767445054">"Informacije o željenoj NFC usluzi za plaćanje"</string>
- <string name="permdesc_preferredPaymentInfo" msgid="8583552469807294967">"Dozvoljava aplikaciji da preuzima informacije o željenoj NFC usluzi za plaćanje, poput registrovanih identifikatora aplikacija i odredišta preusmeravanja."</string>
- <string name="permlab_nfc" msgid="1904455246837674977">"kontrola komunikacije u užem polju (Near Field Communication)"</string>
- <string name="permdesc_nfc" msgid="8352737680695296741">"Dozvoljava aplikaciji da komunicira sa oznakama, karticama i čitačima komunikacije kratkog dometa (NFC)."</string>
- <string name="permlab_disableKeyguard" msgid="3605253559020928505">"onemogućavanje zaključavanja ekrana"</string>
- <string name="permdesc_disableKeyguard" msgid="3223710003098573038">"Dozvoljava aplikaciji da onemogući zaključavanje tastature i sve povezane bezbednosne mere sa lozinkama. Na primer, telefon onemogućava zaključavanje tastature pri prijemu dolaznog telefonskog poziva, a zatim ga ponovo omogućava po završetku poziva."</string>
- <string name="permlab_requestPasswordComplexity" msgid="1808977190557794109">"traženje složenosti zaključavanja ekrana"</string>
- <string name="permdesc_requestPasswordComplexity" msgid="1130556896836258567">"Dozvoljava aplikaciji da sazna nivo složenosti zaključavanja ekrana (visoka, srednja, niska ili nijedna), što ukazuje na mogući opseg trajanja i tip zaključavanja ekrana. Aplikacija može i da predlaže korisnicima da ažuriraju zaključavanje ekrana na određeni nivo, ali korisnici slobodno mogu da zanemare to i da idu na druge stranice. Imajte na umu da se podaci za zaključavanje ekrana ne čuvaju kao običan tekst, pa aplikacija ne zna tačnu lozinku."</string>
- <string name="permlab_postNotification" msgid="4875401198597803658">"prikazivanje obaveštenja"</string>
- <string name="permdesc_postNotification" msgid="5974977162462877075">"Dozvoljava aplikaciji da prikazuje obaveštenja"</string>
- <string name="permlab_turnScreenOn" msgid="219344053664171492">"uključivanje ekrana"</string>
- <string name="permdesc_turnScreenOn" msgid="4394606875897601559">"Dozvoljava aplikaciji da uključi ekran."</string>
- <string name="permlab_useBiometric" msgid="6314741124749633786">"koristi biometrijski hardver"</string>
- <string name="permdesc_useBiometric" msgid="7502858732677143410">"Dozvoljava aplikaciji da koristi biometrijski hardver za potvrdu identiteta"</string>
- <string name="permlab_manageFingerprint" msgid="7432667156322821178">"upravljaj hardverom za otiske prstiju"</string>
- <string name="permdesc_manageFingerprint" msgid="2025616816437339865">"Dozvoljava aplikaciji da aktivira metode za dodavanje i brisanje šablona otisaka prstiju koji će se koristiti."</string>
- <string name="permlab_useFingerprint" msgid="1001421069766751922">"koristi hardver za otiske prstiju"</string>
- <string name="permdesc_useFingerprint" msgid="412463055059323742">"Dozvoljava aplikaciji da koristi hardver za otiske prstiju radi potvrde identiteta"</string>
- <string name="permlab_audioWrite" msgid="8501705294265669405">"izmena muzičke kolekcije"</string>
- <string name="permdesc_audioWrite" msgid="8057399517013412431">"Dozvoljava aplikaciji da menja muzičku kolekciju."</string>
- <string name="permlab_videoWrite" msgid="5940738769586451318">"izmena video kolekcije"</string>
- <string name="permdesc_videoWrite" msgid="6124731210613317051">"Dozvoljava aplikaciji da menja video kolekciju."</string>
- <string name="permlab_imagesWrite" msgid="1774555086984985578">"izmena kolekcije slika"</string>
- <string name="permdesc_imagesWrite" msgid="5195054463269193317">"Dozvoljava aplikaciji da menja kolekciju slika."</string>
- <string name="permlab_mediaLocation" msgid="7368098373378598066">"čitanje lokacija iz medijske kolekcije"</string>
- <string name="permdesc_mediaLocation" msgid="597912899423578138">"Dozvoljava aplikaciji da čita lokacije iz medijske kolekcije."</string>
- <string name="biometric_app_setting_name" msgid="3339209978734534457">"Koristite biometriju"</string>
- <string name="biometric_or_screen_lock_app_setting_name" msgid="5348462421758257752">"Koristite biometriju ili zaključavanje ekrana"</string>
- <string name="biometric_dialog_default_title" msgid="55026799173208210">"Potvrdite svoj identitet"</string>
- <string name="biometric_dialog_default_subtitle" msgid="8457232339298571992">"Koristite biometrijski podatak da biste nastavili"</string>
- <string name="biometric_or_screen_lock_dialog_default_subtitle" msgid="159539678371552009">"Koristite biometrijski podatak ili zaključavanje ekrana da biste nastavili"</string>
- <string name="biometric_error_hw_unavailable" msgid="2494077380540615216">"Biometrijski hardver nije dostupan"</string>
- <string name="biometric_error_user_canceled" msgid="6732303949695293730">"Potvrda identiteta je otkazana"</string>
- <string name="biometric_not_recognized" msgid="5106687642694635888">"Nije prepoznato"</string>
- <string name="biometric_error_canceled" msgid="8266582404844179778">"Potvrda identiteta je otkazana"</string>
- <string name="biometric_error_device_not_secured" msgid="3129845065043995924">"Niste podesili ni PIN, ni šablon, ni lozinku"</string>
- <string name="biometric_error_generic" msgid="6784371929985434439">"Greška pri potvrdi identiteta"</string>
- <string name="screen_lock_app_setting_name" msgid="6054944352976789228">"Koristite zaključavanje ekrana"</string>
- <string name="screen_lock_dialog_default_subtitle" msgid="120359538048533695">"Upotrebite zaključavanje ekrana da biste nastavili"</string>
- <string name="fingerprint_acquired_partial" msgid="4323789264604479684">"Jako pritisnite senzor"</string>
- <string name="fingerprint_acquired_insufficient" msgid="623888149088216458">"Prepoznavanje otiska prsta nije uspelo. Probajte ponovo."</string>
- <string name="fingerprint_acquired_imager_dirty" msgid="1770676120848224250">"Obrišite senzor za otisak prsta i probajte ponovo"</string>
- <string name="fingerprint_acquired_imager_dirty_alt" msgid="9169582140486372897">"Obrišite senzor i probajte ponovo"</string>
- <string name="fingerprint_acquired_too_fast" msgid="1628459767349116104">"Jako pritisnite senzor"</string>
- <string name="fingerprint_acquired_too_slow" msgid="6683510291554497580">"Previše sporo ste pomerili prst. Probajte ponovo."</string>
- <string name="fingerprint_acquired_already_enrolled" msgid="2285166003936206785">"Probajte sa drugim otiskom prsta"</string>
- <string name="fingerprint_acquired_too_bright" msgid="3863560181670915607">"Previše je svetlo"</string>
- <string name="fingerprint_acquired_power_press" msgid="3107864151278434961">"Otkriven je pritisak dugmeta za uključivanje"</string>
- <string name="fingerprint_acquired_try_adjusting" msgid="3667006071003809364">"Probajte da prilagodite"</string>
- <string name="fingerprint_acquired_immobile" msgid="1621891895241888048">"Svaki put pomalo promenite položaj prsta"</string>
+ <string name="permgroupdesc_sms" msgid="5726462398070064542">"шаље и прегледа SMS поруке"</string>
+ <string name="permgrouplab_storage" msgid="17339216290379241">"Фајлови"</string>
+ <string name="permgroupdesc_storage" msgid="5378659041354582769">"приступ фајловима на уређају"</string>
+ <string name="permgrouplab_readMediaAural" msgid="1858331312624942053">"Музика и звук"</string>
+ <string name="permgroupdesc_readMediaAural" msgid="7565467343667089595">"приступ музици и аудио садржају на уређају"</string>
+ <string name="permgrouplab_readMediaVisual" msgid="4724874717811908660">"Слике и видео снимци"</string>
+ <string name="permgroupdesc_readMediaVisual" msgid="4080463241903508688">"приступ сликама и видео снимцима на уређају"</string>
+ <string name="permgrouplab_microphone" msgid="2480597427667420076">"Микрофон"</string>
+ <string name="permgroupdesc_microphone" msgid="1047786732792487722">"снима звук"</string>
+ <string name="permgrouplab_activityRecognition" msgid="3324466667921775766">"Физичке активности"</string>
+ <string name="permgroupdesc_activityRecognition" msgid="4725624819457670704">"приступ физичким активностима"</string>
+ <string name="permgrouplab_camera" msgid="9090413408963547706">"Камера"</string>
+ <string name="permgroupdesc_camera" msgid="7585150538459320326">"снима слике и видео"</string>
+ <string name="permgrouplab_nearby_devices" msgid="5529147543651181991">"Уређаји у близини"</string>
+ <string name="permgroupdesc_nearby_devices" msgid="3213561597116913508">"откривање уређаја у близини и повезивање са њима"</string>
+ <string name="permgrouplab_calllog" msgid="7926834372073550288">"Евиденције позива"</string>
+ <string name="permgroupdesc_calllog" msgid="2026996642917801803">"читање и писање евиденције позива на телефону"</string>
+ <string name="permgrouplab_phone" msgid="570318944091926620">"Телефон"</string>
+ <string name="permgroupdesc_phone" msgid="270048070781478204">"упућује телефонске позиве и управља њима"</string>
+ <string name="permgrouplab_sensors" msgid="9134046949784064495">"Сензори за тело"</string>
+ <string name="permgroupdesc_sensors" msgid="2610631290633747752">"приступа подацима сензора о виталним функцијама"</string>
+ <string name="permgrouplab_notifications" msgid="5472972361980668884">"Обавештења"</string>
+ <string name="permgroupdesc_notifications" msgid="4608679556801506580">"приказивање обавештења"</string>
+ <string name="capability_title_canRetrieveWindowContent" msgid="7554282892101587296">"да преузима садржај прозора"</string>
+ <string name="capability_desc_canRetrieveWindowContent" msgid="6195610527625237661">"Проверава садржај прозора са којим остварујете интеракцију."</string>
+ <string name="capability_title_canRequestTouchExploration" msgid="327598364696316213">"да укључи Истраживања додиром"</string>
+ <string name="capability_desc_canRequestTouchExploration" msgid="4394677060796752976">"Ставке које додирнете ће бити изговорене наглас, а можете да се крећете по екрану покретима."</string>
+ <string name="capability_title_canRequestFilterKeyEvents" msgid="2772371671541753254">"да прати текст који уносите"</string>
+ <string name="capability_desc_canRequestFilterKeyEvents" msgid="2381315802405773092">"Обухвата личне податке као што су бројеви кредитних картица и лозинке."</string>
+ <string name="capability_title_canControlMagnification" msgid="7701572187333415795">"да управља увећањем приказа"</string>
+ <string name="capability_desc_canControlMagnification" msgid="2206586716709254805">"Управља нивоом зумирања приказа и одређивањем положаја."</string>
+ <string name="capability_title_canPerformGestures" msgid="9106545062106728987">"Обављање покрета"</string>
+ <string name="capability_desc_canPerformGestures" msgid="6619457251067929726">"Може да додирује, листа, скупља приказ и обавља друге покрете."</string>
+ <string name="capability_title_canCaptureFingerprintGestures" msgid="1189053104594608091">"Покрети за отисак прста"</string>
+ <string name="capability_desc_canCaptureFingerprintGestures" msgid="6861869337457461274">"Може да региструје покрете на сензору за отисак прста на уређају."</string>
+ <string name="capability_title_canTakeScreenshot" msgid="3895812893130071930">"Направи снимак екрана"</string>
+ <string name="capability_desc_canTakeScreenshot" msgid="7762297374317934052">"Може да направи снимак екрана."</string>
+ <string name="permlab_statusBar" msgid="8798267849526214017">"онемогућавање или измена статусне траке"</string>
+ <string name="permdesc_statusBar" msgid="5809162768651019642">"Дозвољава апликацији да онемогући статусну траку или да додаје и уклања системске иконе."</string>
+ <string name="permlab_statusBarService" msgid="2523421018081437981">"функционисање као статусна трака"</string>
+ <string name="permdesc_statusBarService" msgid="6652917399085712557">"Дозвољава апликацији да функционише као статусна трака."</string>
+ <string name="permlab_expandStatusBar" msgid="1184232794782141698">"проширење/скупљање статусне траке"</string>
+ <string name="permdesc_expandStatusBar" msgid="7180756900448498536">"Дозвољава апликацији да проширује или скупља статусну траку."</string>
+ <string name="permlab_fullScreenIntent" msgid="4310888199502509104">"приказује обавештења као активности преко целог екрана на закључаном уређају"</string>
+ <string name="permdesc_fullScreenIntent" msgid="1100721419406643997">"Омогућава апликацији да на закључаном уређају приказује обавештења као активности преко целог екрана."</string>
+ <string name="permlab_install_shortcut" msgid="7451554307502256221">"Инсталирање пречица"</string>
+ <string name="permdesc_install_shortcut" msgid="4476328467240212503">"да додају пречице на почетни екран без интервенције корисника."</string>
+ <string name="permlab_uninstall_shortcut" msgid="295263654781900390">"деинсталирање пречица"</string>
+ <string name="permdesc_uninstall_shortcut" msgid="1924735350988629188">"Омогућава апликацији да уклања пречице са почетног екрана без интервенције корисника."</string>
+ <string name="permlab_processOutgoingCalls" msgid="4075056020714266558">"преусмеравање одлазних позива"</string>
+ <string name="permdesc_processOutgoingCalls" msgid="7833149750590606334">"Дозвољава апликацији да види који број се бира при одлазном позиву уз опцију да преусмери позив на други број или га потпуно прекине."</string>
+ <string name="permlab_answerPhoneCalls" msgid="4131324833663725855">"одговарај на телефонске позиве"</string>
+ <string name="permdesc_answerPhoneCalls" msgid="894386681983116838">"Дозвољава апликацији да одговори на долазни телефонски позив."</string>
+ <string name="permlab_receiveSms" msgid="505961632050451881">"пријем текстуалних порука (SMS)"</string>
+ <string name="permdesc_receiveSms" msgid="1797345626687832285">"Дозвољава апликацији да прима и обрађује SMS поруке. То значи да апликација може да надгледа или брише поруке које се шаљу уређају, а да вам их не прикаже."</string>
+ <string name="permlab_receiveMms" msgid="4000650116674380275">"пријем текстуалних порука (MMS)"</string>
+ <string name="permdesc_receiveMms" msgid="958102423732219710">"Дозвољава апликацији да прима и обрађује MMS поруке. То значи да апликација може да надгледа или брише поруке које се шаљу уређају, а да вам их не прикаже."</string>
+ <string name="permlab_bindCellBroadcastService" msgid="586746677002040651">"Прослеђивање порука за мобилне уређаје на локалитету"</string>
+ <string name="permdesc_bindCellBroadcastService" msgid="6540910200973641606">"Дозвољава апликацији да се везује за модул порука за мобилне уређаје на локалитету да би прослеђивала поруке за мобилне уређаје на локалитету онако како су примљене. Обавештења порука за мобилне уређаје на локалитету се на неким локацијама примају као упозорења на хитне случајеве. Злонамерне апликације могу да утичу на перформансе или ометају рад уређаја када се прими порука о хитном случају за мобилне уређаје на локалитету."</string>
+ <string name="permlab_manageOngoingCalls" msgid="281244770664231782">"Управљање одлазним позивима"</string>
+ <string name="permdesc_manageOngoingCalls" msgid="7003138133829915265">"Омогућава апликацији да види детаље о одлазним позивима на уређају и да контролише те позиве."</string>
+ <string name="permlab_readCellBroadcasts" msgid="5869884450872137693">"читање порука инфо сервиса"</string>
+ <string name="permdesc_readCellBroadcasts" msgid="672513437331980168">"Омогућава апликацији да чита поруке инфо сервиса које уређај прима. Упозорења инфо сервиса се на неким локацијама примају као упозорења на хитне случајеве. Злонамерне апликације могу да утичу на перформансе или ометају функционисање уређаја када се прими порука инфо сервиса о хитном случају."</string>
+ <string name="permlab_subscribedFeedsRead" msgid="217624769238425461">"читање пријављених фидова"</string>
+ <string name="permdesc_subscribedFeedsRead" msgid="6911349196661811865">"Дозвољава апликацији да преузима детаље о тренутно синхронизованим фидовима."</string>
+ <string name="permlab_sendSms" msgid="7757368721742014252">"шаље и прегледа SMS поруке"</string>
+ <string name="permdesc_sendSms" msgid="6757089798435130769">"Дозвољава апликацији да шаље SMS поруке. Ово може да доведе до неочекиваних трошкова. Злонамерне апликације могу да шаљу поруке без ваше потврде, што може да изазове трошкове."</string>
+ <string name="permlab_readSms" msgid="5164176626258800297">"читање текстуалних порука (SMS или MMS)"</string>
+ <string name="permdesc_readSms" product="tablet" msgid="7912990447198112829">"Ова апликација може да чита све SMS (текстуалне) поруке које се чувају на таблету."</string>
+ <string name="permdesc_readSms" product="tv" msgid="3054753345758011986">"Ова апликација може да чита све SMS (текстуалне) поруке које се чувају на Android TV уређају."</string>
+ <string name="permdesc_readSms" product="default" msgid="774753371111699782">"Ова апликација може да чита све SMS (текстуалне) поруке које се чувају на телефону."</string>
+ <string name="permlab_receiveWapPush" msgid="4223747702856929056">"пријем текстуалних порука (WAP)"</string>
+ <string name="permdesc_receiveWapPush" msgid="1638677888301778457">"Дозвољава апликацији да прима и обрађује WAP поруке. Ова дозвола укључује могућност праћења или брисања порука које вам се шаљу, а које вам се не приказују."</string>
+ <string name="permlab_getTasks" msgid="7460048811831750262">"преузимање покренутих апликација"</string>
+ <string name="permdesc_getTasks" msgid="7388138607018233726">"Дозвољава апликацији да преузима информације о актуелним и недавно покренутим задацима. Ово може да омогући апликацији да открије информације о томе које се апликације користе на уређају."</string>
+ <string name="permlab_manageProfileAndDeviceOwners" msgid="639849495253987493">"управљање власницима профила и уређаја"</string>
+ <string name="permdesc_manageProfileAndDeviceOwners" msgid="7304240671781989283">"Дозвољава апликацији да подеси власнике профила и власника уређаја."</string>
+ <string name="permlab_reorderTasks" msgid="7598562301992923804">"промена редоследа покренутих апликација"</string>
+ <string name="permdesc_reorderTasks" msgid="8796089937352344183">"Дозвољава апликацији да премешта задатке у први план и у позадину. Апликација може да ради ово без вашег уноса."</string>
+ <string name="permlab_enableCarMode" msgid="893019409519325311">"омогућавање режима рада у аутомобилу"</string>
+ <string name="permdesc_enableCarMode" msgid="56419168820473508">"Дозвољава апликацији да омогући режим рада у аутомобилу."</string>
+ <string name="permlab_killBackgroundProcesses" msgid="6559320515561928348">"затварање других апликација"</string>
+ <string name="permdesc_killBackgroundProcesses" msgid="2357013583055434685">"Дозвољава апликацији да заустави позадинске процесе других апликација. Ово може да заустави друге апликације."</string>
+ <string name="permlab_systemAlertWindow" msgid="5757218350944719065">"Ова апликација може да се приказује преко других апликација"</string>
+ <string name="permdesc_systemAlertWindow" msgid="1145660714855738308">"Ова апликација може да се приказује преко других апликација или других делова делова екрана. То може да омета стандардно коришћење апликација и начин на који се друге апликације приказују."</string>
+ <string name="permlab_runInBackground" msgid="541863968571682785">"покретање у позадини"</string>
+ <string name="permdesc_runInBackground" msgid="4344539472115495141">"Ова апликација може да се покреће у позадини. То може брже да истроши батерију."</string>
+ <string name="permlab_useDataInBackground" msgid="783415807623038947">"коришћење података у позадини"</string>
+ <string name="permdesc_useDataInBackground" msgid="1230753883865891987">"Ова апликација може да користи податке у позадини. То може да повећа потрошњу података."</string>
+ <string name="permlab_schedule_exact_alarm" msgid="6683283918033029730">"Заказивање временски прецизних радњи"</string>
+ <string name="permdesc_schedule_exact_alarm" msgid="8198009212013211497">"Ова апликација може да закаже да се рад догоди у жељено време у будућности. То значи и да апликација може да ради када не користите активно уређај."</string>
+ <string name="permlab_use_exact_alarm" msgid="348045139777131552">"Заказивање аларма или подсетника за догађаје"</string>
+ <string name="permdesc_use_exact_alarm" msgid="7033761461886938912">"Ова апликација може да заказује радње попут аларма и подсетника да би вас обавештавала у жељено време у будућности."</string>
+ <string name="permlab_persistentActivity" msgid="464970041740567970">"омогућавање непрекидне активности апликације"</string>
+ <string name="permdesc_persistentActivity" product="tablet" msgid="6055271149187369916">"Дозвољава апликацији да учини сопствене компоненте трајним у меморији. Ово може да ограничи меморију доступну другим апликацијама и успори таблет."</string>
+ <string name="permdesc_persistentActivity" product="tv" msgid="6800526387664131321">"Дозвољава апликацији да трајно задржи неке своје делове у меморији. Ово може да ограничи меморију доступну другим апликацијама и успори Android TV уређај."</string>
+ <string name="permdesc_persistentActivity" product="default" msgid="1914841924366562051">"Дозвољава апликацији да учини сопствене компоненте трајним у меморији. Ово може да ограничи меморију доступну другим апликацијама и успори телефон."</string>
+ <string name="permlab_foregroundService" msgid="1768855976818467491">"покрени услугу у првом плану"</string>
+ <string name="permdesc_foregroundService" msgid="8720071450020922795">"Дозвољава апликацији да користи услуге у првом плану."</string>
+ <string name="permlab_foregroundServiceCamera" msgid="7814751737955715297">"покретање услуге у првом плану која припада типу „camera“"</string>
+ <string name="permdesc_foregroundServiceCamera" msgid="6973701931250595727">"Дозвољава апликацији да користи услуге у првом плану које припадају типу „camera“"</string>
+ <string name="permlab_foregroundServiceConnectedDevice" msgid="3019650546176872501">"покретање услуге у првом плану која припада типу „connectedDevice“"</string>
+ <string name="permdesc_foregroundServiceConnectedDevice" msgid="1067457315741352963">"Дозвољава апликацији да користи услуге у првом плану које припадају типу „connectedDevice“"</string>
+ <string name="permlab_foregroundServiceDataSync" msgid="5847463514326881076">"покретање услуге у првом плану која припада типу „dataSync“"</string>
+ <string name="permdesc_foregroundServiceDataSync" msgid="2267140263423973050">"Дозвољава апликацији да користи услуге у првом плану које припадају типу „dataSync“"</string>
+ <string name="permlab_foregroundServiceLocation" msgid="3745428302378535690">"покретање услуге у првом плану која припада типу „location“"</string>
+ <string name="permdesc_foregroundServiceLocation" msgid="118894034365177183">"Дозвољава апликацији да користи услуге у првом плану које припадају типу „location“"</string>
+ <string name="permlab_foregroundServiceMediaPlayback" msgid="4002687983891935514">"покретање услуге у првом плану која припада типу „mediaPlayback“"</string>
+ <string name="permdesc_foregroundServiceMediaPlayback" msgid="3638032446063968043">"Дозвољава апликацији да користи услуге у првом плану које припадају типу „mediaPlayback“"</string>
+ <string name="permlab_foregroundServiceMediaProjection" msgid="2630868915733312527">"покретање услуге у првом плану која припада типу „mediaProjection“"</string>
+ <string name="permdesc_foregroundServiceMediaProjection" msgid="4805677128082002298">"Дозвољава апликацији да користи услуге у првом плану које припадају типу „mediaProjection“"</string>
+ <string name="permlab_foregroundServiceMicrophone" msgid="7390033424890545399">"покретање услуге у првом плану која припада типу „microphone“"</string>
+ <string name="permdesc_foregroundServiceMicrophone" msgid="1206041516173483201">"Дозвољава апликацији да користи услуге у првом плану које припадају типу „microphone“"</string>
+ <string name="permlab_foregroundServicePhoneCall" msgid="627937743867697892">"покретање услуге у првом плану која припада типу „phoneCall“"</string>
+ <string name="permdesc_foregroundServicePhoneCall" msgid="5941660252587015147">"Дозвољава апликацији да користи услуге у првом плану које припадају типу „phoneCall“"</string>
+ <string name="permlab_foregroundServiceHealth" msgid="3675776442080928184">"покретање услуге у првом плану која припада типу „health“"</string>
+ <string name="permdesc_foregroundServiceHealth" msgid="2024586220562667185">"Дозвољава апликацији да користи услуге у првом плану које припадају типу „health“"</string>
+ <string name="permlab_foregroundServiceRemoteMessaging" msgid="105670277002780950">"покретање услуге у првом плану која припада типу „remoteMessaging“"</string>
+ <string name="permdesc_foregroundServiceRemoteMessaging" msgid="8767598075877576277">"Дозвољава апликацији да користи услуге у првом плану које припадају типу „remoteMessaging“"</string>
+ <string name="permlab_foregroundServiceSystemExempted" msgid="1597663713590612685">"покретање услуге у првом плану која припада типу „systemExempted“"</string>
+ <string name="permdesc_foregroundServiceSystemExempted" msgid="947381760834649622">"Дозвољава апликацији да користи услуге у првом плану које припадају типу „systemExempted“"</string>
+ <string name="permlab_foregroundServiceFileManagement" msgid="2585000987966045030">"покретање услуге у првом плану која припада типу „fileManagement“"</string>
+ <string name="permdesc_foregroundServiceFileManagement" msgid="417103601269698508">"Дозвољава апликацији да користи услуге у првом плану које припадају типу „fileManagement“"</string>
+ <string name="permlab_foregroundServiceSpecialUse" msgid="7973536745876645082">"покретање услуге у првом плану која припада типу „specialUse“"</string>
+ <string name="permdesc_foregroundServiceSpecialUse" msgid="646713654541885919">"Дозвољава апликацији да користи услуге у првом плану које припадају типу „specialUse“"</string>
+ <string name="permlab_getPackageSize" msgid="375391550792886641">"мерење меморијског простора у апликацији"</string>
+ <string name="permdesc_getPackageSize" msgid="742743530909966782">"Дозвољава апликацији да преузме величине кôда, података и кеша."</string>
+ <string name="permlab_writeSettings" msgid="8057285063719277394">"измена подешавања система"</string>
+ <string name="permdesc_writeSettings" msgid="8293047411196067188">"Дозвољава апликацији да мења податке о подешавању система. Злонамерне апликације могу да оштете конфигурацију система."</string>
+ <string name="permlab_receiveBootCompleted" msgid="6643339400247325379">"покретање при покретању система"</string>
+ <string name="permdesc_receiveBootCompleted" product="tablet" msgid="5565659082718177484">"Омогућава да се апликација покрене одмах након покретања система. То може да успори покретање таблета, при чему ова апликација може да успори функционисање целог таблета тиме што ће увек бити активна."</string>
+ <string name="permdesc_receiveBootCompleted" product="tv" msgid="4900842256047614307">"Дозвољава апликацији да се покрене одмах по укључивању система. То може да успори покретање Android TV уређаја и апликација може да успори функционисање уређаја у целини тако што ће увек бити активна."</string>
+ <string name="permdesc_receiveBootCompleted" product="default" msgid="7912677044558690092">"Омогућава да се апликација покрене чим се систем покрене. То може да успори покретање телефона, при чему ова апликација може да успори функционисање целог телефона тиме што ће увек бити активна."</string>
+ <string name="permlab_broadcastSticky" msgid="4552241916400572230">"слање пријемчивих емитовања"</string>
+ <string name="permdesc_broadcastSticky" product="tablet" msgid="5058486069846384013">"Дозвољава апликацији да шаље пријемчива емитовања, која остају по завршетку емитовања. Прекомерна употреба може да успори или дестабилизује таблет тако што ће га приморати да троши превише меморије."</string>
+ <string name="permdesc_broadcastSticky" product="tv" msgid="2338185920171000650">"Дозвољава апликацији да шаље лепљива емитовања која остају по завршетку емитовања. Прекомерна употреба може да успори или дестабилизује Android TV уређај тако што ће га приморати да троши превише меморије."</string>
+ <string name="permdesc_broadcastSticky" product="default" msgid="134529339678913453">"Дозвољава апликацији да шаље пријемчива емитовања, која остају по завршетку емитовања. Прекомерна употреба може да успори или дестабилизује телефон тако што ће га приморати да троши превише меморије."</string>
+ <string name="permlab_readContacts" msgid="8776395111787429099">"читање контаката"</string>
+ <string name="permdesc_readContacts" product="tablet" msgid="6430093481659992692">"Дозвољава апликацији да чита податке о контактима које чувате на таблету. Апликације ће имати приступ и налозима на вашем таблету на којима су направљени контакти. Ту могу да спадају налози које су отвориле апликације које сте инсталирали. Ова дозвола омогућава апликацијама да чувају податке о контактима и злонамерне апликације могу да деле податке о контактима без вашег знања."</string>
+ <string name="permdesc_readContacts" product="tv" msgid="8400138591135554789">"Дозвољава апликацији да чита податке о контактима које чувате на Android TV уређају. Апликације ће имати приступ и налозима на вашем Android TV уређају на којима су направљени контакти. Ту могу да спадају налози које су отвориле апликације које сте инсталирали. Ова дозвола омогућава апликацијама да чувају податке о контактима и злонамерне апликације могу да деле податке о контактима без вашег знања."</string>
+ <string name="permdesc_readContacts" product="default" msgid="4911989776203207644">"Дозвољава апликацији да чита податке о контактима које чувате на телефону. Апликације ће имати приступ и налозима на вашем телефону на којима су направљени контакти. Ту могу да спадају налози које су отвориле апликације које сте инсталирали. Ова дозвола омогућава апликацијама да чувају податке о контактима и злонамерне апликације могу да деле податке о контактима без вашег знања."</string>
+ <string name="permlab_writeContacts" msgid="8919430536404830430">"измена контаката"</string>
+ <string name="permdesc_writeContacts" product="tablet" msgid="6422419281427826181">"Дозвољава апликацији да мења податке о контактима које чувате на таблету. Ова дозвола омогућава апликацијама да бришу податке о контактима."</string>
+ <string name="permdesc_writeContacts" product="tv" msgid="6488872735379978935">"Дозвољава апликацији да мења податке о контактима које чувате на Android TV уређају. Ова дозвола омогућава апликацијама да бришу податке о контактима."</string>
+ <string name="permdesc_writeContacts" product="default" msgid="8304795696237065281">"Дозвољава апликацији да мења податке о контактима које чувате на телефону. Ова дозвола омогућава апликацијама да бришу податке о контактима."</string>
+ <string name="permlab_readCallLog" msgid="1739990210293505948">"читање евиденције позива"</string>
+ <string name="permdesc_readCallLog" msgid="8964770895425873433">"Ова апликација може да чита историју позива."</string>
+ <string name="permlab_writeCallLog" msgid="670292975137658895">"писање евиденције позива"</string>
+ <string name="permdesc_writeCallLog" product="tablet" msgid="2657525794731690397">"Дозвољава апликацији да мења евиденцију позива на таблету, укључујући податке о долазним и одлазним позивима. Злонамерне апликације могу ово да користе да би брисале или мењале евиденцију позива."</string>
+ <string name="permdesc_writeCallLog" product="tv" msgid="3934939195095317432">"Дозвољава апликацији да мења евиденцију позива на Android TV уређају, укључујући податке о долазним и одлазним позивима. Злонамерне апликације могу ово да користе за брисање или мењање евиденције позива."</string>
+ <string name="permdesc_writeCallLog" product="default" msgid="5903033505665134802">"Дозвољава апликацији да мења евиденцију позива на телефону, укључујући податке о долазним и одлазним позивима. Злонамерне апликације могу ово да користе да би брисале или мењале евиденцију позива."</string>
+ <string name="permlab_bodySensors" msgid="662918578601619569">"Приступ подацима сензора за тело, као што је пулс, у току коришћења"</string>
+ <string name="permdesc_bodySensors" product="default" msgid="7652650410295512140">"Дозвољава апликацији да приступа подацима сензора за тело, као што су пулс, температура и проценат кисеоника у крви док се апликација користи."</string>
+ <string name="permlab_bodySensors_background" msgid="4912560779957760446">"Приступ подацима сензора за тело, као што је пулс, у позадини"</string>
+ <string name="permdesc_bodySensors_background" product="default" msgid="8870726027557749417">"Дозвољава апликацији да приступа подацима сензора за тело, као што су пулс, температура и проценат кисеоника у крви док је апликација у позадини."</string>
+ <string name="permlab_readCalendar" msgid="6408654259475396200">"Читање догађаја и података из календара"</string>
+ <string name="permdesc_readCalendar" product="tablet" msgid="515452384059803326">"Ова апликација може да чита све догађаје из календара које чувате на таблету, као и да дели или чува податке из календара."</string>
+ <string name="permdesc_readCalendar" product="tv" msgid="5811726712981647628">"Ова апликација може да чита све догађаје из календара које чувате на Android TV уређају, као и да дели или чува податке из календара."</string>
+ <string name="permdesc_readCalendar" product="default" msgid="9118823807655829957">"Ова апликација може да чита све догађаје из календара које чувате на телефону, као и да дели или чува податке из календара."</string>
+ <string name="permlab_writeCalendar" msgid="6422137308329578076">"додавање или измена календарских догађаја и слање порука е-поште гостима без знања власника"</string>
+ <string name="permdesc_writeCalendar" product="tablet" msgid="8722230940717092850">"Ова апликацији може да додаје, уклања или мења догађаје из календара на таблету. Ова апликација може да шаље поруке које изгледају као да их шаљу власници календара или да мења догађаје без знања власника."</string>
+ <string name="permdesc_writeCalendar" product="tv" msgid="951246749004952706">"Ова апликација може да додаје, уклања или мења догађаје из календара на Android TV уређају. Ова апликација може да шаље поруке које изгледају као да их шаљу власници календара или да мења догађаје без знања власника."</string>
+ <string name="permdesc_writeCalendar" product="default" msgid="5416380074475634233">"Ова апликацији може да додаје, уклања или мења догађаје из календара на телефону. Ова апликација може да шаље поруке које изгледају као да их шаљу власници календара или да мења догађаје без знања власника."</string>
+ <string name="permlab_accessLocationExtraCommands" msgid="5162339812057983988">"приступ додатним командама добављача локације"</string>
+ <string name="permdesc_accessLocationExtraCommands" msgid="355369611979907967">"Омогућава апликацији да приступа додатним командама даваоца услуга локације. То може да омогући апликацији да утиче на рад GPS-а или других извора локације."</string>
+ <string name="permlab_accessFineLocation" msgid="6426318438195622966">"приступ прецизној локацији само у првом плану"</string>
+ <string name="permdesc_accessFineLocation" msgid="6732174080240016335">"Ова апликација може да одреди вашу тачну локацију на основу услуга локације док се апликација користи. Услуге локације за уређај морају да буду укључене да би апликација одредила локацију. То може да повећа потрошњу батерије."</string>
+ <string name="permlab_accessCoarseLocation" msgid="1561042925407799741">"приступ приближној локацији само у првом плану"</string>
+ <string name="permdesc_accessCoarseLocation" msgid="778521847873199160">"Ова апликација може да одреди вашу приближну локацију на основу услуга локације док се апликација користи. Услуге локације за уређај морају да буду укључене да би апликација одредила локацију."</string>
+ <string name="permlab_accessBackgroundLocation" msgid="1721164702777366138">"приступ локацији у позадини"</string>
+ <string name="permdesc_accessBackgroundLocation" msgid="8264885066095638105">"Ова апликација може да приступа локацији у било ком тренутку, чак и док се апликација не користи."</string>
+ <string name="permlab_modifyAudioSettings" msgid="6129039778010031815">"промена аудио подешавања"</string>
+ <string name="permdesc_modifyAudioSettings" msgid="8687227609663124921">"Дозвољава апликацији да мења глобална аудио подешавања као што су јачина звука и избор звучника који се користи као излаз."</string>
+ <string name="permlab_recordAudio" msgid="1208457423054219147">"снимање аудио записа"</string>
+ <string name="permdesc_recordAudio" msgid="5857246765327514062">"Ова апликација може да снима звук помоћу микрофона док се апликација користи."</string>
+ <string name="permlab_recordBackgroundAudio" msgid="5891032812308878254">"да снима звук у позадини"</string>
+ <string name="permdesc_recordBackgroundAudio" msgid="1992623135737407516">"Ова апликација може да снима звук помоћу микрофона у било ком тренутку."</string>
+ <string name="permlab_detectScreenCapture" msgid="4447042362828799433">"откривање снимања екрана у прозорима апликација"</string>
+ <string name="permdesc_detectScreenCapture" msgid="3485784917960342284">"Ако се током коришћења ове апликације направи снимак екрана, апликација ће добити обавештење."</string>
+ <string name="permlab_sim_communication" msgid="176788115994050692">"слање команди на SIM"</string>
+ <string name="permdesc_sim_communication" msgid="4179799296415957960">"Омогућава апликацији да шаље команде SIM картици. То је веома опасно."</string>
+ <string name="permlab_activityRecognition" msgid="1782303296053990884">"препознавање физичких активности"</string>
+ <string name="permdesc_activityRecognition" msgid="8667484762991357519">"Ова апликација може да препозна физичке активности."</string>
+ <string name="permlab_camera" msgid="6320282492904119413">"снимање фотографија и видео снимака"</string>
+ <string name="permdesc_camera" msgid="5240801376168647151">"Ова апликација може да снима слике и видео снимке помоћу камере док се апликација користи."</string>
+ <string name="permlab_backgroundCamera" msgid="7549917926079731681">"да снима слике и видео снимке у позадини"</string>
+ <string name="permdesc_backgroundCamera" msgid="1615291686191138250">"Ова апликација може да снима фотографије и видео снимке помоћу камере у било ком тренутку."</string>
+ <string name="permlab_systemCamera" msgid="3642917457796210580">"Дозволите некој апликацији или услузи да приступа камерама система да би снимала слике и видео снимке"</string>
+ <string name="permdesc_systemCamera" msgid="5938360914419175986">"Ова привилегована системска апликација може да снима слике и видео снимке помоћу камере система у било ком тренутку. Апликација треба да има и дозволу android.permission.CAMERA"</string>
+ <string name="permlab_cameraOpenCloseListener" msgid="5548732769068109315">"Дозволите апликацији или услузи да добија повратне позиве о отварању или затварању уређаја са камером."</string>
+ <string name="permdesc_cameraOpenCloseListener" msgid="2002636131008772908">"Ова апликација може да добија повратне позиве када се било који уређај са камером отвара или затвара (помоћу неке апликације)."</string>
+ <string name="permlab_vibrate" msgid="8596800035791962017">"контрола вибрације"</string>
+ <string name="permdesc_vibrate" msgid="8733343234582083721">"Дозвољава апликацији да контролише вибрацију."</string>
+ <string name="permdesc_vibrator_state" msgid="7050024956594170724">"Дозвољава апликацији да приступа стању вибрирања."</string>
+ <string name="permlab_callPhone" msgid="1798582257194643320">"директно позивање бројева телефона"</string>
+ <string name="permdesc_callPhone" msgid="5439809516131609109">"Дозвољава апликацији да позива бројеве телефона без ваше дозволе. Ово може да доведе до неочекиваних трошкова или позива. Имајте на уму да ово не дозвољава апликацији да позива бројеве за хитне случајеве. Злонамерне апликације могу да позивају без ваше потврде, што може да доведе до трошкова."</string>
+ <string name="permlab_accessImsCallService" msgid="442192920714863782">"приступ услузи позива помоћу размене тренутних порука"</string>
+ <string name="permdesc_accessImsCallService" msgid="6328551241649687162">"Дозвољава апликацији да користи услугу размене тренутних порука да би упућивала позиве без ваше интервенције."</string>
+ <string name="permlab_readPhoneState" msgid="8138526903259297969">"читање статуса и идентитета телефона"</string>
+ <string name="permdesc_readPhoneState" msgid="7229063553502788058">"Дозвољава апликацији да приступа функцијама телефона на уређају. Ова дозвола омогућава апликацији да утврди број телефона и ИД-ове уређаја, затим да ли је позив активан, као и број даљинског уређаја са којим је успостављен позив."</string>
+ <string name="permlab_readBasicPhoneState" msgid="3214853233263871347">"очитавање основног телефонског статуса и идентитета"</string>
+ <string name="permdesc_readBasicPhoneState" msgid="828185691675460520">"Омогућава апликацији да приступа основним телефонским функцијама уређаја."</string>
+ <string name="permlab_manageOwnCalls" msgid="9033349060307561370">"преусмеравање позива преко система"</string>
+ <string name="permdesc_manageOwnCalls" msgid="4431178362202142574">"Дозвољава апликацији да преусмерава позиве преко система да би побољшала доживљај позивања."</string>
+ <string name="permlab_callCompanionApp" msgid="3654373653014126884">"преглед и контрола позива преко система."</string>
+ <string name="permdesc_callCompanionApp" msgid="8474168926184156261">"Дозвољава апликацији да прегледа и контролише тренутне позиве на уређају. То обухвата информације попут бројева телефона и статуса позива."</string>
+ <string name="permlab_exemptFromAudioRecordRestrictions" msgid="1164725468350759486">"изузимање из ограничења за снимање звука"</string>
+ <string name="permdesc_exemptFromAudioRecordRestrictions" msgid="2425117015896871976">"Изузмите апликацију из ограничења за снимање звука."</string>
+ <string name="permlab_acceptHandover" msgid="2925523073573116523">"настави позив у другој апликацији"</string>
+ <string name="permdesc_acceptHandovers" msgid="7129026180128626870">"Дозвољава апликацији да настави позив који је започет у другој апликацији."</string>
+ <string name="permlab_readPhoneNumbers" msgid="5668704794723365628">"читање бројева телефона"</string>
+ <string name="permdesc_readPhoneNumbers" msgid="7368652482818338871">"Дозвољава апликацији да приступа бројевима телефона на уређају."</string>
+ <string name="permlab_wakeLock" product="automotive" msgid="1904736682319375676">"не искључуј екран у аутомобилу"</string>
+ <string name="permlab_wakeLock" product="tablet" msgid="1527660973931694000">"спречавање преласка таблета у стање спавања"</string>
+ <string name="permlab_wakeLock" product="tv" msgid="2856941418123343518">"спречава Android TV уређај да пређе у стање спавања"</string>
+ <string name="permlab_wakeLock" product="default" msgid="569409726861695115">"спречавање преласка телефона у стање спавања"</string>
+ <string name="permdesc_wakeLock" product="automotive" msgid="5995045369683254571">"Дозвољава апликацији да не искључује екран у аутомобилу."</string>
+ <string name="permdesc_wakeLock" product="tablet" msgid="2441742939101526277">"Дозвољава апликацији да спречи таблет да пређе у стање спавања."</string>
+ <string name="permdesc_wakeLock" product="tv" msgid="2329298966735118796">"Дозвољава апликацији да спречи Android TV уређај да пређе у стање спавања."</string>
+ <string name="permdesc_wakeLock" product="default" msgid="3689523792074007163">"Дозвољава апликацији да спречи телефон да пређе у стање спавања."</string>
+ <string name="permlab_transmitIr" msgid="8077196086358004010">"пренос инфрацрвених зрака"</string>
+ <string name="permdesc_transmitIr" product="tablet" msgid="5884738958581810253">"Дозвољава апликацији да користи одашиљач инфрацрвених зрака таблета."</string>
+ <string name="permdesc_transmitIr" product="tv" msgid="3278506969529173281">"Дозвољава да апликација користи одашиљач инфрацрвених зрака на Android TV уређају."</string>
+ <string name="permdesc_transmitIr" product="default" msgid="8484193849295581808">"Дозвољава апликацији да користи одашиљач инфрацрвених зрака телефона."</string>
+ <string name="permlab_setWallpaper" msgid="6959514622698794511">"подешавање позадине"</string>
+ <string name="permdesc_setWallpaper" msgid="2973996714129021397">"Дозвољава апликацији да поставља позадину система."</string>
+ <string name="permlab_setWallpaperHints" msgid="1153485176642032714">"прилагођавање величине позадине"</string>
+ <string name="permdesc_setWallpaperHints" msgid="6257053376990044668">"Дозвољава апликацији да подеси савете за системску величину позадине."</string>
+ <string name="permlab_setTimeZone" msgid="7922618798611542432">"подешавање временске зоне"</string>
+ <string name="permdesc_setTimeZone" product="tablet" msgid="1788868809638682503">"Дозвољава апликацији да промени временску зону таблета."</string>
+ <string name="permdesc_setTimeZone" product="tv" msgid="9069045914174455938">"Дозвољава апликацији да мења временску зону Android TV уређаја."</string>
+ <string name="permdesc_setTimeZone" product="default" msgid="4611828585759488256">"Дозвољава апликацији да промени временску зону телефона."</string>
+ <string name="permlab_getAccounts" msgid="5304317160463582791">"проналажење налога на уређају"</string>
+ <string name="permdesc_getAccounts" product="tablet" msgid="1784452755887604512">"Дозвољава апликацији да преузима листу налога познатих таблету. Ово може да обухвата било које налоге које праве апликације које инсталирате."</string>
+ <string name="permdesc_getAccounts" product="tv" msgid="437604680436540822">"Дозвољава апликацији да дође до листе налога познатих Android TV уређају. Ово може да обухвата све налоге које отварају апликације које сте инсталирали."</string>
+ <string name="permdesc_getAccounts" product="default" msgid="2491273043569751867">"Дозвољава апликацији да преузима листу налога познатих телефону. Ово може да обухвата било које налоге које праве апликације које инсталирате."</string>
+ <string name="permlab_accessNetworkState" msgid="2349126720783633918">"преглед мрежних веза"</string>
+ <string name="permdesc_accessNetworkState" msgid="4394564702881662849">"Дозвољава апликацији да прегледа информације о мрежним везама као што су информације о томе које мреже постоје и које мреже су повезане."</string>
+ <string name="permlab_createNetworkSockets" msgid="3224420491603590541">"има пун мрежни приступ"</string>
+ <string name="permdesc_createNetworkSockets" msgid="7722020828749535988">"Дозвољава апликацији да прави мрежне прикључке и користи прилагођене мрежне протоколе. Прегледач и друге апликације омогућавају слање података на Интернет, па ова дозвола није потребна за слање података на Интернет."</string>
+ <string name="permlab_changeNetworkState" msgid="8945711637530425586">"промена везе са мрежом"</string>
+ <string name="permdesc_changeNetworkState" msgid="649341947816898736">"Дозвољава апликацији да мења статус повезивања са мрежом."</string>
+ <string name="permlab_changeTetherState" msgid="9079611809931863861">"промена повезивања привезивањем"</string>
+ <string name="permdesc_changeTetherState" msgid="3025129606422533085">"Дозвољава апликацији да мења статус везе са привезаном мрежом."</string>
+ <string name="permlab_accessWifiState" msgid="5552488500317911052">"преглед WiFi веза"</string>
+ <string name="permdesc_accessWifiState" msgid="6913641669259483363">"Дозвољава апликацији да прегледа информације о WiFi умрежавању, као што су информације о томе да ли је WiFi омогућен и називи повезаних WiFi уређаја."</string>
+ <string name="permlab_changeWifiState" msgid="7947824109713181554">"повезивање и прекид везе са WiFi мрежом"</string>
+ <string name="permdesc_changeWifiState" msgid="7170350070554505384">"Дозвољава апликацији да се повезује са приступним тачкама за WiFi и прекида везу са њима, као и да уноси промене у конфигурацију уређаја за WiFi мреже."</string>
+ <string name="permlab_changeWifiMulticastState" msgid="285626875870754696">"омогућавање пријема вишесмерног WiFi саобраћаја"</string>
+ <string name="permdesc_changeWifiMulticastState" product="tablet" msgid="191079868596433554">"Дозвољава апликацији да прима пакете који се шаљу на све уређаје на WiFi мрежи помоћу вишесмерних адреса, а не само на таблет. Користи више напајања од режима једносмерног саобраћаја."</string>
+ <string name="permdesc_changeWifiMulticastState" product="tv" msgid="1336952358450652595">"Дозвољава апликацији да прима пакете који се шаљу на све уређаје на WiFi мрежи помоћу вишесмерних адреса, а не само на Android TV уређај. Користи више енергије од режима без вишесмерног слања."</string>
+ <string name="permdesc_changeWifiMulticastState" product="default" msgid="8296627590220222740">"Дозвољава апликацији да прима пакете који се шаљу на све уређаје на WiFi мрежи помоћу вишесмерних адреса, а не само на телефон. Користи више напајања од режима једносмерног саобраћаја."</string>
+ <string name="permlab_bluetoothAdmin" msgid="6490373569441946064">"приступ Bluetooth подешавањима"</string>
+ <string name="permdesc_bluetoothAdmin" product="tablet" msgid="5370837055438574863">"Дозвољава апликацији да конфигурише локални Bluetooth таблет, као и да открије даљинске уређаје и упари се са њима."</string>
+ <string name="permdesc_bluetoothAdmin" product="tv" msgid="1623992984547014588">"Дозвољава апликацији да конфигурише Bluetooth на Android TV уређају и да открије удаљене уређаје и упари се са њима."</string>
+ <string name="permdesc_bluetoothAdmin" product="default" msgid="7381341743021234863">"Дозвољава апликацији да конфигурише локални Bluetooth телефон, као и да открије даљинске уређаје и упари се са њима."</string>
+ <string name="permlab_accessWimaxState" msgid="7029563339012437434">"повезивање и прекид везе са WiMAX-ом"</string>
+ <string name="permdesc_accessWimaxState" msgid="5372734776802067708">"Дозвољава апликацији да утврди да ли је WiMAX омогућен, као и информације о било којим повезаним WiMAX мрежама."</string>
+ <string name="permlab_changeWimaxState" msgid="6223305780806267462">"промени WiMAX статуса"</string>
+ <string name="permdesc_changeWimaxState" product="tablet" msgid="4011097664859480108">"Дозвољава апликацији да повезује таблет са WiMAX мрежама и прекида везе са њима."</string>
+ <string name="permdesc_changeWimaxState" product="tv" msgid="5373274458799425276">"Дозвољава апликацији да повезује Android TV уређај са WiMAX мрежама и да прекида ту везу."</string>
+ <string name="permdesc_changeWimaxState" product="default" msgid="1551666203780202101">"Дозвољава апликацији да повезује телефон са WiMAX мрежама и прекида везе са њима."</string>
+ <string name="permlab_bluetooth" msgid="586333280736937209">"упаривање са Bluetooth уређајима"</string>
+ <string name="permdesc_bluetooth" product="tablet" msgid="3053222571491402635">"Дозвољава апликацији да прегледа конфигурацију Bluetooth-а на таблету, као и да успоставља и прихвата везе са упареним уређајима."</string>
+ <string name="permdesc_bluetooth" product="tv" msgid="8851534496561034998">"Дозвољава апликацији да прегледа конфигурацију Bluetooth-а на Android TV уређају и да успоставља и прихвата везе са упареним уређајима."</string>
+ <string name="permdesc_bluetooth" product="default" msgid="2779606714091276746">"Дозвољава апликацији да прегледа конфигурацију Bluetooth-а на телефону, као и да успоставља и прихвата везе са упареним уређајима."</string>
+ <string name="permlab_bluetooth_scan" msgid="5402587142833124594">"откривање и упаривање са оближњим Bluetooth уређ."</string>
+ <string name="permdesc_bluetooth_scan" product="default" msgid="6540723536925289276">"Дозвољава апликацији да открива Bluetooth уређаје у близини и упарује се са њима"</string>
+ <string name="permlab_bluetooth_connect" msgid="6657463246355003528">"повезивање са упареним Bluetooth уређајима"</string>
+ <string name="permdesc_bluetooth_connect" product="default" msgid="4546016548795544617">"Дозвољава апликацији да се повезује са упареним Bluetooth уређајима"</string>
+ <string name="permlab_bluetooth_advertise" msgid="2781147747928853177">"оглашавање на Bluetooth уређајима у близини"</string>
+ <string name="permdesc_bluetooth_advertise" product="default" msgid="6085174451034210183">"Дозвољава апликацији да се оглашава на Bluetooth уређајима у близини"</string>
+ <string name="permlab_uwb_ranging" msgid="8141915781475770665">"одређивање раздаљине између уређаја ултра-широког појаса у близини"</string>
+ <string name="permdesc_uwb_ranging" msgid="2519723069604307055">"Дозвољава апликацији да одређује релативну раздаљину између уређаја ултра-широког појаса у близини"</string>
+ <string name="permlab_nearby_wifi_devices" msgid="392774237063608500">"интеракција са WiFi уређајима у близини"</string>
+ <string name="permdesc_nearby_wifi_devices" msgid="3054307728646332906">"Дозвољава апликацији да се оглашава, повезује и утврђује релативну позицију WiFi уређаја у близини"</string>
+ <string name="permlab_preferredPaymentInfo" msgid="5274423844767445054">"Информације о жељеној NFC услузи за плаћање"</string>
+ <string name="permdesc_preferredPaymentInfo" msgid="8583552469807294967">"Дозвољава апликацији да преузима информације о жељеној NFC услузи за плаћање, попут регистрованих идентификатора апликација и одредишта преусмеравања."</string>
+ <string name="permlab_nfc" msgid="1904455246837674977">"контрола комуникације у ужем пољу (Near Field Communication)"</string>
+ <string name="permdesc_nfc" msgid="8352737680695296741">"Дозвољава апликацији да комуницира са ознакама, картицама и читачима комуникације кратког домета (NFC)."</string>
+ <string name="permlab_disableKeyguard" msgid="3605253559020928505">"онемогућавање закључавања екрана"</string>
+ <string name="permdesc_disableKeyguard" msgid="3223710003098573038">"Дозвољава апликацији да онемогући закључавање тастатуре и све повезане безбедносне мере са лозинкама. На пример, телефон онемогућава закључавање тастатуре при пријему долазног телефонског позива, а затим га поново омогућава по завршетку позива."</string>
+ <string name="permlab_requestPasswordComplexity" msgid="1808977190557794109">"тражење сложености закључавања екрана"</string>
+ <string name="permdesc_requestPasswordComplexity" msgid="1130556896836258567">"Дозвољава апликацији да сазна ниво сложености закључавања екрана (висока, средња, ниска или ниједна), што указује на могући опсег трајања и тип закључавања екрана. Апликација може и да предлаже корисницима да ажурирају закључавање екрана на одређени ниво, али корисници слободно могу да занемаре то и да иду на друге странице. Имајте на уму да се подаци за закључавање екрана не чувају као обичан текст, па апликација не зна тачну лозинку."</string>
+ <string name="permlab_postNotification" msgid="4875401198597803658">"приказивање обавештења"</string>
+ <string name="permdesc_postNotification" msgid="5974977162462877075">"Дозвољава апликацији да приказује обавештења"</string>
+ <string name="permlab_turnScreenOn" msgid="219344053664171492">"укључивање екрана"</string>
+ <string name="permdesc_turnScreenOn" msgid="4394606875897601559">"Дозвољава апликацији да укључи екран."</string>
+ <string name="permlab_useBiometric" msgid="6314741124749633786">"користи биометријски хардвер"</string>
+ <string name="permdesc_useBiometric" msgid="7502858732677143410">"Дозвољава апликацији да користи биометријски хардвер за потврду идентитета"</string>
+ <string name="permlab_manageFingerprint" msgid="7432667156322821178">"управљај хардвером за отиске прстију"</string>
+ <string name="permdesc_manageFingerprint" msgid="2025616816437339865">"Дозвољава апликацији да активира методе за додавање и брисање шаблона отисака прстију који ће се користити."</string>
+ <string name="permlab_useFingerprint" msgid="1001421069766751922">"користи хардвер за отиске прстију"</string>
+ <string name="permdesc_useFingerprint" msgid="412463055059323742">"Дозвољава апликацији да користи хардвер за отиске прстију ради потврде идентитета"</string>
+ <string name="permlab_audioWrite" msgid="8501705294265669405">"измена музичке колекције"</string>
+ <string name="permdesc_audioWrite" msgid="8057399517013412431">"Дозвољава апликацији да мења музичку колекцију."</string>
+ <string name="permlab_videoWrite" msgid="5940738769586451318">"измена видео колекције"</string>
+ <string name="permdesc_videoWrite" msgid="6124731210613317051">"Дозвољава апликацији да мења видео колекцију."</string>
+ <string name="permlab_imagesWrite" msgid="1774555086984985578">"измена колекције слика"</string>
+ <string name="permdesc_imagesWrite" msgid="5195054463269193317">"Дозвољава апликацији да мења колекцију слика."</string>
+ <string name="permlab_mediaLocation" msgid="7368098373378598066">"читање локација из медијске колекције"</string>
+ <string name="permdesc_mediaLocation" msgid="597912899423578138">"Дозвољава апликацији да чита локације из медијске колекције."</string>
+ <string name="biometric_app_setting_name" msgid="3339209978734534457">"Користите биометрију"</string>
+ <string name="biometric_or_screen_lock_app_setting_name" msgid="5348462421758257752">"Користите биометрију или закључавање екрана"</string>
+ <string name="biometric_dialog_default_title" msgid="55026799173208210">"Потврдите свој идентитет"</string>
+ <string name="biometric_dialog_default_subtitle" msgid="8457232339298571992">"Користите биометријски податак да бисте наставили"</string>
+ <string name="biometric_or_screen_lock_dialog_default_subtitle" msgid="159539678371552009">"Користите биометријски податак или закључавање екрана да бисте наставили"</string>
+ <string name="biometric_error_hw_unavailable" msgid="2494077380540615216">"Биометријски хардвер није доступан"</string>
+ <string name="biometric_error_user_canceled" msgid="6732303949695293730">"Потврда идентитета је отказана"</string>
+ <string name="biometric_not_recognized" msgid="5106687642694635888">"Није препознато"</string>
+ <string name="biometric_error_canceled" msgid="8266582404844179778">"Потврда идентитета је отказана"</string>
+ <string name="biometric_error_device_not_secured" msgid="3129845065043995924">"Нисте подесили ни PIN, ни шаблон, ни лозинку"</string>
+ <string name="biometric_error_generic" msgid="6784371929985434439">"Грешка при потврди идентитета"</string>
+ <string name="screen_lock_app_setting_name" msgid="6054944352976789228">"Користите закључавање екрана"</string>
+ <string name="screen_lock_dialog_default_subtitle" msgid="120359538048533695">"Употребите закључавање екрана да бисте наставили"</string>
+ <string name="fingerprint_acquired_partial" msgid="4323789264604479684">"Јако притисните сензор"</string>
+ <string name="fingerprint_acquired_insufficient" msgid="623888149088216458">"Препознавање отиска прста није успело. Пробајте поново."</string>
+ <string name="fingerprint_acquired_imager_dirty" msgid="1770676120848224250">"Обришите сензор за отисак прста и пробајте поново"</string>
+ <string name="fingerprint_acquired_imager_dirty_alt" msgid="9169582140486372897">"Обришите сензор и пробајте поново"</string>
+ <string name="fingerprint_acquired_too_fast" msgid="1628459767349116104">"Јако притисните сензор"</string>
+ <string name="fingerprint_acquired_too_slow" msgid="6683510291554497580">"Превише споро сте померили прст. Пробајте поново."</string>
+ <string name="fingerprint_acquired_already_enrolled" msgid="2285166003936206785">"Пробајте са другим отиском прста"</string>
+ <string name="fingerprint_acquired_too_bright" msgid="3863560181670915607">"Превише је светло"</string>
+ <string name="fingerprint_acquired_power_press" msgid="3107864151278434961">"Откривен је притисак дугмета за укључивање"</string>
+ <string name="fingerprint_acquired_try_adjusting" msgid="3667006071003809364">"Пробајте да прилагодите"</string>
+ <string name="fingerprint_acquired_immobile" msgid="1621891895241888048">"Сваки пут помало промените положај прста"</string>
<string-array name="fingerprint_acquired_vendor">
</string-array>
- <string name="fingerprint_error_not_match" msgid="4599441812893438961">"Otisak prsta nije prepoznat"</string>
- <string name="fingerprint_udfps_error_not_match" msgid="8236930793223158856">"Otisak prsta nije prepoznat"</string>
- <string name="fingerprint_authenticated" msgid="2024862866860283100">"Otisak prsta je potvrđen"</string>
- <string name="face_authenticated_no_confirmation_required" msgid="8867889115112348167">"Lice je potvrđeno"</string>
- <string name="face_authenticated_confirmation_required" msgid="6872632732508013755">"Lice je potvrđeno. Pritisnite Potvrdi"</string>
- <string name="fingerprint_error_hw_not_available" msgid="4571700896929561202">"Hardver za otiske prstiju nije dostupan."</string>
- <string name="fingerprint_error_no_space" msgid="7285481581905967580">"Podešavanje otiska prsta nije uspelo"</string>
- <string name="fingerprint_error_timeout" msgid="7361192266621252164">"Vreme za podešavanje otiska prsta je isteklo. Probajte ponovo."</string>
- <string name="fingerprint_error_canceled" msgid="540026881380070750">"Radnja sa otiskom prsta je otkazana."</string>
- <string name="fingerprint_error_user_canceled" msgid="7685676229281231614">"Korisnik je otkazao radnju sa otiskom prsta."</string>
- <string name="fingerprint_error_lockout" msgid="6626753679019351368">"Previše pokušaja. Koristite zaključavanje ekrana umesto toga."</string>
- <string name="fingerprint_error_lockout_permanent" msgid="9060651300306264843">"Previše pokušaja. Koristite zaključavanje ekrana umesto toga."</string>
- <string name="fingerprint_error_unable_to_process" msgid="2446280592818621224">"Obrađivanje otiska prsta nije uspelo. Probajte ponovo."</string>
- <string name="fingerprint_error_no_fingerprints" msgid="8671811719699072411">"Nije registrovan nijedan otisak prsta."</string>
- <string name="fingerprint_error_hw_not_present" msgid="578914350967423382">"Ovaj uređaj nema senzor za otisak prsta."</string>
- <string name="fingerprint_error_security_update_required" msgid="7750187320640856433">"Senzor je privremeno onemogućen."</string>
- <string name="fingerprint_error_bad_calibration" msgid="4385512597740168120">"Ne možete da koristite senzor za otisak prsta. Posetite dobavljača za popravke"</string>
- <string name="fingerprint_error_power_pressed" msgid="5479524500542129414">"Pritisnuto je dugme za uključivanje"</string>
- <string name="fingerprint_name_template" msgid="8941662088160289778">"Prst <xliff:g id="FINGERID">%d</xliff:g>"</string>
- <string name="fingerprint_app_setting_name" msgid="4253767877095495844">"Koristite otisak prsta"</string>
- <string name="fingerprint_or_screen_lock_app_setting_name" msgid="3501743523487644907">"Koristite otisak prsta ili zaključavanje ekrana"</string>
- <string name="fingerprint_dialog_default_subtitle" msgid="3879832845486835905">"Nastavite pomoću otiska prsta"</string>
- <string name="fingerprint_or_screen_lock_dialog_default_subtitle" msgid="5195808203117992200">"Koristite otisak prsta ili zaključavanje ekrana da biste nastavili"</string>
+ <string name="fingerprint_error_not_match" msgid="4599441812893438961">"Отисак прста није препознат"</string>
+ <string name="fingerprint_udfps_error_not_match" msgid="8236930793223158856">"Отисак прста није препознат"</string>
+ <string name="fingerprint_authenticated" msgid="2024862866860283100">"Отисак прста је потврђен"</string>
+ <string name="face_authenticated_no_confirmation_required" msgid="8867889115112348167">"Лице је потврђено"</string>
+ <string name="face_authenticated_confirmation_required" msgid="6872632732508013755">"Лице је потврђено. Притисните Потврди"</string>
+ <string name="fingerprint_error_hw_not_available" msgid="4571700896929561202">"Хардвер за отиске прстију није доступан."</string>
+ <string name="fingerprint_error_no_space" msgid="7285481581905967580">"Подешавање отиска прста није успело"</string>
+ <string name="fingerprint_error_timeout" msgid="7361192266621252164">"Време за подешавање отиска прста је истекло. Пробајте поново."</string>
+ <string name="fingerprint_error_canceled" msgid="540026881380070750">"Радња са отиском прста је отказана."</string>
+ <string name="fingerprint_error_user_canceled" msgid="7685676229281231614">"Корисник је отказао радњу са отиском прста."</string>
+ <string name="fingerprint_error_lockout" msgid="6626753679019351368">"Превише покушаја. Користите закључавање екрана уместо тога."</string>
+ <string name="fingerprint_error_lockout_permanent" msgid="9060651300306264843">"Превише покушаја. Користите закључавање екрана уместо тога."</string>
+ <string name="fingerprint_error_unable_to_process" msgid="2446280592818621224">"Обрађивање отиска прста није успело. Пробајте поново."</string>
+ <string name="fingerprint_error_no_fingerprints" msgid="8671811719699072411">"Није регистрован ниједан отисак прста."</string>
+ <string name="fingerprint_error_hw_not_present" msgid="578914350967423382">"Овај уређај нема сензор за отисак прста."</string>
+ <string name="fingerprint_error_security_update_required" msgid="7750187320640856433">"Сензор је привремено онемогућен."</string>
+ <string name="fingerprint_error_bad_calibration" msgid="4385512597740168120">"Не можете да користите сензор за отисак прста. Посетите добављача за поправке"</string>
+ <string name="fingerprint_error_power_pressed" msgid="5479524500542129414">"Притиснуто је дугме за укључивање"</string>
+ <string name="fingerprint_name_template" msgid="8941662088160289778">"Прст <xliff:g id="FINGERID">%d</xliff:g>"</string>
+ <string name="fingerprint_app_setting_name" msgid="4253767877095495844">"Користите отисак прста"</string>
+ <string name="fingerprint_or_screen_lock_app_setting_name" msgid="3501743523487644907">"Користите отисак прста или закључавање екрана"</string>
+ <string name="fingerprint_dialog_default_subtitle" msgid="3879832845486835905">"Наставите помоћу отиска прста"</string>
+ <string name="fingerprint_or_screen_lock_dialog_default_subtitle" msgid="5195808203117992200">"Користите отисак прста или закључавање екрана да бисте наставили"</string>
<string-array name="fingerprint_error_vendor">
</string-array>
- <string name="fingerprint_error_vendor_unknown" msgid="4170002184907291065">"Došlo je do problema. Probajte ponovo."</string>
- <string name="fingerprint_icon_content_description" msgid="4741068463175388817">"Ikona otiska prsta"</string>
- <string name="face_recalibrate_notification_name" msgid="7311163114750748686">"Otključavanje licem"</string>
- <string name="face_recalibrate_notification_title" msgid="2524791952735579082">"Problem sa otključavanje licem"</string>
- <string name="face_recalibrate_notification_content" msgid="3064513770251355594">"Dodirnite da biste izbrisali model lica, pa ponovo dodajte svoje lice"</string>
- <string name="face_setup_notification_title" msgid="8843461561970741790">"Podesite otključavanje licem"</string>
- <string name="face_setup_notification_content" msgid="5463999831057751676">"Otključajte telefon tako što ćete ga pogledati"</string>
- <string name="face_sensor_privacy_enabled" msgid="7407126963510598508">"Da biste koristili otključavanje licem, uključite "<b>"pristup kameri"</b>" u odeljku Podešavanja > Privatnost"</string>
- <string name="fingerprint_setup_notification_title" msgid="2002630611398849495">"Podesite još načina za otključavanje"</string>
- <string name="fingerprint_setup_notification_content" msgid="205578121848324852">"Dodirnite da biste dodali otisak prsta"</string>
- <string name="fingerprint_recalibrate_notification_name" msgid="1414578431898579354">"Otključavanje otiskom prsta"</string>
- <string name="fingerprint_recalibrate_notification_title" msgid="2406561052064558497">"Ne možete da koristite senzor za otisak prsta"</string>
- <string name="fingerprint_recalibrate_notification_content" msgid="8519935717822194943">"Posetite dobavljača za popravke."</string>
- <string name="face_acquired_insufficient" msgid="6889245852748492218">"Pravljenje modela lica nije uspelo. Probajte ponovo."</string>
- <string name="face_acquired_too_bright" msgid="8070756048978079164">"Previše je svetlo. Probajte sa slabijim osvetljenjem."</string>
- <string name="face_acquired_too_dark" msgid="8539853432479385326">"Nema dovoljno svetla"</string>
- <string name="face_acquired_too_close" msgid="4453646176196302462">"Udaljite telefon"</string>
- <string name="face_acquired_too_far" msgid="2922278214231064859">"Približite telefon"</string>
- <string name="face_acquired_too_high" msgid="8278815780046368576">"Pomerite telefon nagore"</string>
- <string name="face_acquired_too_low" msgid="4075391872960840081">"Pomerite telefon nadole"</string>
- <string name="face_acquired_too_right" msgid="6245286514593540859">"Pomerite telefon ulevo"</string>
- <string name="face_acquired_too_left" msgid="9201762240918405486">"Pomerite telefon udesno"</string>
- <string name="face_acquired_poor_gaze" msgid="4427153558773628020">"Gledajte pravo u uređaj."</string>
- <string name="face_acquired_not_detected" msgid="1057966913397548150">"Ne vidi se lice. Držite telefon u visini očiju."</string>
- <string name="face_acquired_too_much_motion" msgid="8199691445085189528">"Mnogo se pomerate. Držite telefon mirno."</string>
- <string name="face_acquired_recalibrate" msgid="8724013080976469746">"Ponovo registrujte lice."</string>
- <string name="face_acquired_too_different" msgid="2520389515612972889">"Lice nije prepoznato. Probajte ponovo."</string>
- <string name="face_acquired_too_similar" msgid="8882920552674125694">"Malo pomerite glavu"</string>
- <string name="face_acquired_pan_too_extreme" msgid="5417928604710621088">"Gledajte pravo u telefon"</string>
- <string name="face_acquired_tilt_too_extreme" msgid="5715715666540716620">"Gledajte pravo u telefon"</string>
- <string name="face_acquired_roll_too_extreme" msgid="8261939882838881194">"Gledajte pravo u telefon"</string>
- <string name="face_acquired_obscured" msgid="4917643294953326639">"Uklonite sve što vam zaklanja lice."</string>
- <string name="face_acquired_sensor_dirty" msgid="8968391891086721678">"Očistite gornji deo ekrana, uključujući crnu traku"</string>
+ <string name="fingerprint_error_vendor_unknown" msgid="4170002184907291065">"Дошло је до проблема. Пробајте поново."</string>
+ <string name="fingerprint_icon_content_description" msgid="4741068463175388817">"Икона отиска прста"</string>
+ <string name="face_recalibrate_notification_name" msgid="7311163114750748686">"Откључавање лицем"</string>
+ <string name="face_recalibrate_notification_title" msgid="2524791952735579082">"Проблем са откључавање лицем"</string>
+ <string name="face_recalibrate_notification_content" msgid="3064513770251355594">"Додирните да бисте избрисали модел лица, па поново додајте своје лице"</string>
+ <string name="face_setup_notification_title" msgid="8843461561970741790">"Подесите откључавање лицем"</string>
+ <string name="face_setup_notification_content" msgid="5463999831057751676">"Откључајте телефон тако што ћете га погледати"</string>
+ <string name="face_sensor_privacy_enabled" msgid="7407126963510598508">"Да бисте користили откључавање лицем, укључите "<b>"приступ камери"</b>" у одељку Подешавања > Приватност"</string>
+ <string name="fingerprint_setup_notification_title" msgid="2002630611398849495">"Подесите још начина за откључавање"</string>
+ <string name="fingerprint_setup_notification_content" msgid="205578121848324852">"Додирните да бисте додали отисак прста"</string>
+ <string name="fingerprint_recalibrate_notification_name" msgid="1414578431898579354">"Откључавање отиском прста"</string>
+ <string name="fingerprint_recalibrate_notification_title" msgid="2406561052064558497">"Не можете да користите сензор за отисак прста"</string>
+ <string name="fingerprint_recalibrate_notification_content" msgid="8519935717822194943">"Посетите добављача за поправке."</string>
+ <string name="face_acquired_insufficient" msgid="6889245852748492218">"Прављење модела лица није успело. Пробајте поново."</string>
+ <string name="face_acquired_too_bright" msgid="8070756048978079164">"Превише је светло. Пробајте са слабијим осветљењем."</string>
+ <string name="face_acquired_too_dark" msgid="8539853432479385326">"Нема довољно светла"</string>
+ <string name="face_acquired_too_close" msgid="4453646176196302462">"Удаљите телефон"</string>
+ <string name="face_acquired_too_far" msgid="2922278214231064859">"Приближите телефон"</string>
+ <string name="face_acquired_too_high" msgid="8278815780046368576">"Померите телефон нагоре"</string>
+ <string name="face_acquired_too_low" msgid="4075391872960840081">"Померите телефон надоле"</string>
+ <string name="face_acquired_too_right" msgid="6245286514593540859">"Померите телефон улево"</string>
+ <string name="face_acquired_too_left" msgid="9201762240918405486">"Померите телефон удесно"</string>
+ <string name="face_acquired_poor_gaze" msgid="4427153558773628020">"Гледајте право у уређај."</string>
+ <string name="face_acquired_not_detected" msgid="1057966913397548150">"Не види се лице. Држите телефон у висини очију."</string>
+ <string name="face_acquired_too_much_motion" msgid="8199691445085189528">"Много се померате. Држите телефон мирно."</string>
+ <string name="face_acquired_recalibrate" msgid="8724013080976469746">"Поново региструјте лице."</string>
+ <string name="face_acquired_too_different" msgid="2520389515612972889">"Лице није препознато. Пробајте поново."</string>
+ <string name="face_acquired_too_similar" msgid="8882920552674125694">"Мало померите главу"</string>
+ <string name="face_acquired_pan_too_extreme" msgid="5417928604710621088">"Гледајте право у телефон"</string>
+ <string name="face_acquired_tilt_too_extreme" msgid="5715715666540716620">"Гледајте право у телефон"</string>
+ <string name="face_acquired_roll_too_extreme" msgid="8261939882838881194">"Гледајте право у телефон"</string>
+ <string name="face_acquired_obscured" msgid="4917643294953326639">"Уклоните све што вам заклања лице."</string>
+ <string name="face_acquired_sensor_dirty" msgid="8968391891086721678">"Очистите горњи део екрана, укључујући црну траку"</string>
<!-- no translation found for face_acquired_dark_glasses_detected (5643703296620631986) -->
<skip />
<!-- no translation found for face_acquired_mouth_covering_detected (8219428572168642593) -->
<skip />
- <string name="face_acquired_recalibrate_alt" msgid="5702674220280332115">"Pravljenje modela lica nije uspelo. Probajte ponovo."</string>
- <string name="face_acquired_dark_glasses_detected_alt" msgid="4052123776406041972">"Otkrivene su tamne naočari. Lice mora da bude potpuno vidljivo."</string>
- <string name="face_acquired_mouth_covering_detected_alt" msgid="1122294982850589766">"Otkriveno je prekrivanje lica. Lice mora da bude potpuno vidljivo."</string>
+ <string name="face_acquired_recalibrate_alt" msgid="5702674220280332115">"Прављење модела лица није успело. Пробајте поново."</string>
+ <string name="face_acquired_dark_glasses_detected_alt" msgid="4052123776406041972">"Откривене су тамне наочари. Лице мора да буде потпуно видљиво."</string>
+ <string name="face_acquired_mouth_covering_detected_alt" msgid="1122294982850589766">"Откривено је прекривање лица. Лице мора да буде потпуно видљиво."</string>
<string-array name="face_acquired_vendor">
</string-array>
- <string name="face_error_hw_not_available" msgid="5085202213036026288">"Provera lica nije uspela. Hardver nije dostupan."</string>
- <string name="face_error_timeout" msgid="2598544068593889762">"Probajte ponovo otključavanje licem"</string>
- <string name="face_error_no_space" msgid="5649264057026021723">"Novi podaci o licu nisu sačuvani. Prvo izbrišete prethodne."</string>
- <string name="face_error_canceled" msgid="2164434737103802131">"Obrada lica je otkazana."</string>
- <string name="face_error_user_canceled" msgid="5766472033202928373">"Korisnik je otkazao otključavanje licem"</string>
- <string name="face_error_lockout" msgid="7864408714994529437">"Previše pokušaja. Probajte ponovo kasnije."</string>
- <string name="face_error_lockout_permanent" msgid="3277134834042995260">"Previše pokušaja. Otključavanje licem je onemogućeno."</string>
- <string name="face_error_lockout_screen_lock" msgid="5062609811636860928">"Previše pokušaja. Koristite zaključavanje ekrana za to."</string>
- <string name="face_error_unable_to_process" msgid="5723292697366130070">"Provera lica nije uspela. Probajte ponovo."</string>
- <string name="face_error_not_enrolled" msgid="1134739108536328412">"Niste podesili otključavanje licem"</string>
- <string name="face_error_hw_not_present" msgid="7940978724978763011">"Otključavanje licem nije podržano na ovom uređaju"</string>
- <string name="face_error_security_update_required" msgid="5076017208528750161">"Senzor je privremeno onemogućen."</string>
- <string name="face_name_template" msgid="3877037340223318119">"Lice <xliff:g id="FACEID">%d</xliff:g>"</string>
- <string name="face_app_setting_name" msgid="5854024256907828015">"Koristite otključavanje licem"</string>
- <string name="face_or_screen_lock_app_setting_name" msgid="1603149075605709106">"Koristite zaključavanje licem ili zaključavanje ekrana"</string>
- <string name="face_dialog_default_subtitle" msgid="6620492813371195429">"Potvrdite identitet licem da biste nastavili"</string>
- <string name="face_or_screen_lock_dialog_default_subtitle" msgid="5006381531158341844">"Koristite lice ili zaključavanje ekrana da biste nastavili"</string>
+ <string name="face_error_hw_not_available" msgid="5085202213036026288">"Провера лица није успела. Хардвер није доступан."</string>
+ <string name="face_error_timeout" msgid="2598544068593889762">"Пробајте поново откључавање лицем"</string>
+ <string name="face_error_no_space" msgid="5649264057026021723">"Нови подаци о лицу нису сачувани. Прво избришете претходне."</string>
+ <string name="face_error_canceled" msgid="2164434737103802131">"Обрада лица је отказана."</string>
+ <string name="face_error_user_canceled" msgid="5766472033202928373">"Корисник је отказао откључавање лицем"</string>
+ <string name="face_error_lockout" msgid="7864408714994529437">"Превише покушаја. Пробајте поново касније."</string>
+ <string name="face_error_lockout_permanent" msgid="3277134834042995260">"Превише покушаја. Откључавање лицем је онемогућено."</string>
+ <string name="face_error_lockout_screen_lock" msgid="5062609811636860928">"Превише покушаја. Користите закључавање екрана за то."</string>
+ <string name="face_error_unable_to_process" msgid="5723292697366130070">"Провера лица није успела. Пробајте поново."</string>
+ <string name="face_error_not_enrolled" msgid="1134739108536328412">"Нисте подесили откључавање лицем"</string>
+ <string name="face_error_hw_not_present" msgid="7940978724978763011">"Откључавање лицем није подржано на овом уређају"</string>
+ <string name="face_error_security_update_required" msgid="5076017208528750161">"Сензор је привремено онемогућен."</string>
+ <string name="face_name_template" msgid="3877037340223318119">"Лице <xliff:g id="FACEID">%d</xliff:g>"</string>
+ <string name="face_app_setting_name" msgid="5854024256907828015">"Користите откључавање лицем"</string>
+ <string name="face_or_screen_lock_app_setting_name" msgid="1603149075605709106">"Користите закључавање лицем или закључавање екрана"</string>
+ <string name="face_dialog_default_subtitle" msgid="6620492813371195429">"Потврдите идентитет лицем да бисте наставили"</string>
+ <string name="face_or_screen_lock_dialog_default_subtitle" msgid="5006381531158341844">"Користите лице или закључавање екрана да бисте наставили"</string>
<string-array name="face_error_vendor">
</string-array>
- <string name="face_error_vendor_unknown" msgid="7387005932083302070">"Došlo je do problema. Probajte ponovo."</string>
- <string name="face_icon_content_description" msgid="465030547475916280">"Ikona lica"</string>
- <string name="permlab_readSyncSettings" msgid="6250532864893156277">"čitanje podešavanja sinhronizacije"</string>
- <string name="permdesc_readSyncSettings" msgid="1325658466358779298">"Dozvoljava aplikaciji da čita podešavanja sinhronizacije za nalog. Na primer, ovako može da se utvrdi da li je aplikacija Ljudi sinhronizovana sa nalogom."</string>
- <string name="permlab_writeSyncSettings" msgid="6583154300780427399">"uključivanje i isključivanje sinhronizacije"</string>
- <string name="permdesc_writeSyncSettings" msgid="6029151549667182687">"Dozvoljava aplikaciji da menja podešavanja sinhronizacije za nalog. Na primer, ovako može da se omogući sinhronizacija aplikacije Ljudi sa nalogom."</string>
- <string name="permlab_readSyncStats" msgid="3747407238320105332">"čitanje statistike o sinhronizaciji"</string>
- <string name="permdesc_readSyncStats" msgid="3867809926567379434">"Dozvoljava aplikaciji da čita statistiku sinhronizacije za nalog, uključujući istoriju sinhronizovanih događaja i količinu podataka koji se sinhronizuju."</string>
- <string name="permlab_sdcardRead" msgid="5791467020950064920">"čitanje sadržaja deljenog memorijskog prostora"</string>
- <string name="permdesc_sdcardRead" msgid="6872973242228240382">"Dozvoljava aplikaciji da čita sadržaj deljenog memorijskog prostora."</string>
- <string name="permlab_readMediaAudio" msgid="8723513075731763810">"čitanje audio fajlova iz deljenog memorijskog prostora"</string>
- <string name="permdesc_readMediaAudio" msgid="5299772574434619399">"Omogućava aplikaciji da čita audio fajlove iz deljenog memorijskog prostora."</string>
- <string name="permlab_readMediaVideo" msgid="7768003311260655007">"čitanje video fajlova iz deljenog memorijskog prostora"</string>
- <string name="permdesc_readMediaVideo" msgid="3846400073770403528">"Omogućava aplikaciji da čita video fajlove iz deljenog memorijskog prostora."</string>
- <string name="permlab_readMediaImages" msgid="4057590631020986789">"čitanje fajlova slika iz deljenog memorijskog prostora"</string>
- <string name="permdesc_readMediaImages" msgid="5836219373138469259">"Omogućava aplikaciji da čita fajlove slika iz deljenog memorijskog prostora."</string>
- <string name="permlab_readVisualUserSelect" msgid="5516204215354667586">"čitanje fajlova slika i video snimaka koje korisnik bira iz deljenog memorijskog prostora"</string>
- <string name="permdesc_readVisualUserSelect" msgid="8027174717714968217">"Omogućava aplikaciji da čita fajlove slika i video snimaka koje izaberete iz deljenog memorijskog prostora."</string>
- <string name="permlab_sdcardWrite" msgid="4863021819671416668">"menjanje ili brisanje sadržaja deljenog memorijskog prostora"</string>
- <string name="permdesc_sdcardWrite" msgid="8376047679331387102">"Dozvoljava aplikaciji da upisuje sadržaj deljenog memorijskog prostora."</string>
- <string name="permlab_use_sip" msgid="8250774565189337477">"upućivanje/prijem SIP poziva"</string>
- <string name="permdesc_use_sip" msgid="3590270893253204451">"Omogućava aplikaciji da upućuje i prima SIP pozive."</string>
- <string name="permlab_register_sim_subscription" msgid="1653054249287576161">"registruje nove veze sa telekomunikacionim mrežama preko SIM kartice"</string>
- <string name="permdesc_register_sim_subscription" msgid="4183858662792232464">"Dozvoljava aplikaciji da registruje nove veze sa telekomunikacionim mrežama preko SIM kartice."</string>
- <string name="permlab_register_call_provider" msgid="6135073566140050702">"registruje nove veze sa telekomunikacionim mrežama"</string>
- <string name="permdesc_register_call_provider" msgid="4201429251459068613">"Dozvoljava aplikaciji da registruje nove veze sa telekomunikacionim mrežama."</string>
- <string name="permlab_connection_manager" msgid="3179365584691166915">"upravljanje vezama sa telekomunikacionim mrežama"</string>
- <string name="permdesc_connection_manager" msgid="1426093604238937733">"Dozvoljava aplikaciji da upravlja vezama sa telekomunikacionim mrežama."</string>
- <string name="permlab_bind_incall_service" msgid="5990625112603493016">"komuniciraj sa ekranom tokom poziva"</string>
- <string name="permdesc_bind_incall_service" msgid="4124917526967765162">"Dozvoljava aplikaciji da kontroliše kada i kako se korisniku prikazuje ekran tokom poziva."</string>
- <string name="permlab_bind_connection_service" msgid="5409268245525024736">"da stupa u interakciju sa telefonskim uslugama"</string>
- <string name="permdesc_bind_connection_service" msgid="6261796725253264518">"Dozvoljava interakciju aplikacije sa telefonskim uslugama radi upućivanja/primanja poziva."</string>
- <string name="permlab_control_incall_experience" msgid="6436863486094352987">"pružaj korisnički doživljaj tokom poziva"</string>
- <string name="permdesc_control_incall_experience" msgid="5896723643771737534">"Dozvoljava aplikaciji da pruža korisnički doživljaj tokom poziva."</string>
- <string name="permlab_readNetworkUsageHistory" msgid="8470402862501573795">"čita istoriju korišćenja mreže"</string>
- <string name="permdesc_readNetworkUsageHistory" msgid="1112962304941637102">"Dozvoljava aplikaciji da čita istoriju korišćenja mreže za posebne mreže i aplikacije."</string>
- <string name="permlab_manageNetworkPolicy" msgid="6872549423152175378">"upravljanje smernicama za mrežu"</string>
- <string name="permdesc_manageNetworkPolicy" msgid="1865663268764673296">"Dozvoljava aplikaciji da upravlja smernicama za mrežu i određuje posebna pravila za aplikaciju."</string>
- <string name="permlab_modifyNetworkAccounting" msgid="7448790834938749041">"izmenite obračunavanje korišćenja mreže"</string>
- <string name="permdesc_modifyNetworkAccounting" msgid="5076042642247205390">"Dozvoljava aplikaciji da izmeni način na koji aplikacije koriste mrežu. Ne koriste je uobičajene aplikacije."</string>
- <string name="permlab_accessNotifications" msgid="7130360248191984741">"pristup obaveštenjima"</string>
- <string name="permdesc_accessNotifications" msgid="761730149268789668">"Dozvoljava aplikaciji da preuzima, ispituje i briše obaveštenja, uključujući ona koja postavljaju druge aplikacije."</string>
- <string name="permlab_bindNotificationListenerService" msgid="5848096702733262458">"povezivanje sa uslugom monitora obaveštenja"</string>
- <string name="permdesc_bindNotificationListenerService" msgid="4970553694467137126">"Dozvoljava vlasniku da se poveže sa interfejsom usluge monitora obaveštenja najvišeg nivoa. Uobičajene aplikacije nikada ne bi trebalo da je koriste."</string>
- <string name="permlab_bindConditionProviderService" msgid="5245421224814878483">"poveži sa uslugom dobavljača uslova"</string>
- <string name="permdesc_bindConditionProviderService" msgid="6106018791256120258">"Dozvoljava vlasniku da se poveže sa interfejsom najvišeg nivoa usluge dobavljača uslova. Ne bi trebalo nikada da bude potrebno za uobičajene aplikacije."</string>
- <string name="permlab_bindDreamService" msgid="4776175992848982706">"povezivanje sa uslugom sanjarenja"</string>
- <string name="permdesc_bindDreamService" msgid="9129615743300572973">"Dozvoljava vlasniku da se poveže sa interfejsom usluge sanjarenja najvišeg nivoa. Uobičajene aplikacije nikada ne bi trebalo da je koriste."</string>
- <string name="permlab_invokeCarrierSetup" msgid="5098810760209818140">"pozivanje aplikacije sa konfiguracijom koju određuje operater"</string>
- <string name="permdesc_invokeCarrierSetup" msgid="4790845896063237887">"Dozvoljava vlasniku da poziva aplikaciju sa konfiguracijom koju određuje operater. Uobičajene aplikacije nikada ne bi trebalo da je koriste."</string>
- <string name="permlab_accessNetworkConditions" msgid="1270732533356286514">"praćenje podataka o uslovima na mreži"</string>
- <string name="permdesc_accessNetworkConditions" msgid="2959269186741956109">"Dozvoljava aplikaciji da prati podatke o uslovima na mreži. Ne bi nikada trebalo da bude potrebno za normalne aplikacije."</string>
- <string name="permlab_setInputCalibration" msgid="932069700285223434">"promeni kalibraciju ulaznog uređaja"</string>
- <string name="permdesc_setInputCalibration" msgid="2937872391426631726">"Dozvoljava aplikaciji da modifikuje parametre kalibracije dodirnog ekrana. Ne bi trebalo da bude potrebno za normalne aplikacije."</string>
- <string name="permlab_accessDrmCertificates" msgid="6473765454472436597">"pristup DRM sertifikatima"</string>
- <string name="permdesc_accessDrmCertificates" msgid="6983139753493781941">"Dozvoljava aplikaciji da dodeljuje i koristi DRM sertifikate. Nikada ne bi trebalo da se koristi za uobičajene aplikacije."</string>
- <string name="permlab_handoverStatus" msgid="7620438488137057281">"prijem statusa prebacivanja pomoću Android prebacivanja"</string>
- <string name="permdesc_handoverStatus" msgid="3842269451732571070">"Dozvoljava ovoj aplikaciji da prima informacije o aktuelnim prebacivanjima pomoću Android prebacivanja"</string>
- <string name="permlab_removeDrmCertificates" msgid="710576248717404416">"uklanjaj DRM sertifikate"</string>
- <string name="permdesc_removeDrmCertificates" msgid="4068445390318355716">"Dozvoljava aplikaciji da uklanja DRM sertifikate. Nikada ne bi trebalo da se koristi za obične aplikacije."</string>
- <string name="permlab_bindCarrierMessagingService" msgid="3363450860593096967">"povezivanje sa uslugom za razmenu poruka mobilnog operatera"</string>
- <string name="permdesc_bindCarrierMessagingService" msgid="6316457028173478345">"Dozvoljava vlasniku da se poveže sa interfejsom najvišeg nivoa za uslugu za razmenu poruka mobilnog operatera. Nikada ne bi trebalo da bude potrebno za standardne aplikacije."</string>
- <string name="permlab_bindCarrierServices" msgid="2395596978626237474">"povezivanje sa uslugama operatera"</string>
- <string name="permdesc_bindCarrierServices" msgid="9185614481967262900">"Dozvoljava vlasniku da se poveže sa uslugama operatera. Nikada ne bi trebalo da bude potrebno za obične aplikacije."</string>
- <string name="permlab_access_notification_policy" msgid="5524112842876975537">"pristupaj podešavanju Ne uznemiravaj"</string>
- <string name="permdesc_access_notification_policy" msgid="8538374112403845013">"Dozvoljava aplikaciji da čita i upisuje konfiguraciju podešavanja Ne uznemiravaj."</string>
- <string name="permlab_startViewPermissionUsage" msgid="1504564328641112341">"početak korišćenja dozvole za pregled"</string>
- <string name="permdesc_startViewPermissionUsage" msgid="2820325605959586538">"Dozvoljava vlasniku da započne korišćenje dozvole za aplikaciju. Nikada ne bi trebalo da bude potrebna za uobičajene aplikacije."</string>
- <string name="permlab_startReviewPermissionDecisions" msgid="8690578688476599284">"pokretanje pregleda odluka o dozvolama"</string>
- <string name="permdesc_startReviewPermissionDecisions" msgid="2775556853503004236">"Dozvoljava vlasniku da pokrene ekran za proveru odluka o dozvolama. Nikada ne bi trebalo da bude potrebno za obične aplikacije."</string>
- <string name="permlab_startViewAppFeatures" msgid="7955084203185903001">"pokretanje prikaza funkcija aplikacije"</string>
- <string name="permdesc_startViewAppFeatures" msgid="7207240860165206107">"Dozvoljava nosiocu dozvole da započne pregledanje informacija o funkcijama aplikacije."</string>
- <string name="permlab_highSamplingRateSensors" msgid="3941068435726317070">"pristup podacima senzora pri velikoj brzini uzorkovanja"</string>
- <string name="permdesc_highSamplingRateSensors" msgid="8430061978931155995">"Dozvoljava aplikaciji da uzima uzorak podataka senzora pri brzini većoj od 200 Hz"</string>
- <string name="policylab_limitPassword" msgid="4851829918814422199">"Podešavanje pravila za lozinku"</string>
- <string name="policydesc_limitPassword" msgid="4105491021115793793">"Kontroliše dužinu i znakove dozvoljene u lozinkama i PIN-ovima za zaključavanje ekrana."</string>
- <string name="policylab_watchLogin" msgid="7599669460083719504">"Nadgledajte pokušaje otključavanja ekrana"</string>
- <string name="policydesc_watchLogin" product="tablet" msgid="2388436408621909298">"Prati broj netačno unetih lozinki prilikom otključavanja ekrana i zaključava tablet ili briše podatke sa tableta ako je netačna lozinka uneta previše puta."</string>
- <string name="policydesc_watchLogin" product="tv" msgid="2140588224468517507">"Nadgleda broj netačnih lozinki unetih pri otključavanju ekrana i zaključava Android TV uređaj ili briše sve podatke sa Android TV uređaja ako se unese previše netačnih lozinki."</string>
- <string name="policydesc_watchLogin" product="automotive" msgid="7011438994051251521">"Prati broj netačno unetih lozinki pri otključavanju ekrana i zaključava sistem za info-zabavu ili briše sve podatke sa sistema za info-zabavu ako je netačna lozinka uneta previše puta."</string>
- <string name="policydesc_watchLogin" product="default" msgid="4885030206253600299">"Prati broj netačno unetih lozinki pri otključavanju ekrana i zaključava telefon ili briše sve podatke sa telefona ako je netačna lozinka uneta previše puta."</string>
- <string name="policydesc_watchLogin_secondaryUser" product="tablet" msgid="2049038943004297474">"Nadgleda broj netačnih lozinki unetih pri otključavanju ekrana i zaključava tablet ili briše sve podatke ovog korisnika ako se unese previše netačnih lozinki."</string>
- <string name="policydesc_watchLogin_secondaryUser" product="tv" msgid="8965224107449407052">"Nadgleda broj netačnih lozinki unetih pri otključavanju ekrana i zaključava Android TV uređaj ili briše sve podatke ovog korisnika ako se unese previše netačnih lozinki."</string>
- <string name="policydesc_watchLogin_secondaryUser" product="automotive" msgid="7180857406058327941">"Nadgleda broj netačnih lozinki unetih pri otključavanju ekrana i zaključava sistem za info-zabavu ili briše sve podatke ovog profila ako se unese previše netačnih lozinki."</string>
- <string name="policydesc_watchLogin_secondaryUser" product="default" msgid="9177645136475155924">"Nadgleda broj netačnih lozinki unetih pri otključavanju ekrana i zaključava telefon ili briše sve podatke ovog korisnika ako se unese previše netačnih lozinki."</string>
- <string name="policylab_resetPassword" msgid="214556238645096520">"Promena zaključavanja ekrana"</string>
- <string name="policydesc_resetPassword" msgid="4626419138439341851">"Menja zaključavanje ekrana."</string>
- <string name="policylab_forceLock" msgid="7360335502968476434">"Zaključavanje ekrana"</string>
- <string name="policydesc_forceLock" msgid="1008844760853899693">"Kontrola načina i vremena zaključavanja ekrana."</string>
- <string name="policylab_wipeData" msgid="1359485247727537311">"Brisanje svih podataka"</string>
- <string name="policydesc_wipeData" product="tablet" msgid="7245372676261947507">"Brisanje podataka na tabletu bez upozorenja resetovanjem na fabrička podešavanja."</string>
- <string name="policydesc_wipeData" product="tv" msgid="513862488950801261">"Briše podatke Android TV uređaja bez upozorenja pomoću resetovanja na fabrička podešavanja."</string>
- <string name="policydesc_wipeData" product="automotive" msgid="660804547737323300">"Briše podatke na sistemu za info-zabavu bez upozorenja resetovanjem na fabrička podešavanja."</string>
- <string name="policydesc_wipeData" product="default" msgid="8036084184768379022">"Brisanje podataka na telefonu bez upozorenja resetovanjem na fabrička podešavanja."</string>
- <string name="policylab_wipeData_secondaryUser" product="automotive" msgid="115034358520328373">"Brisanje podataka profila"</string>
- <string name="policylab_wipeData_secondaryUser" product="default" msgid="413813645323433166">"Obriši podatke korisnika"</string>
- <string name="policydesc_wipeData_secondaryUser" product="tablet" msgid="2336676480090926470">"Briše podatke ovog korisnika na ovom tabletu bez upozorenja."</string>
- <string name="policydesc_wipeData_secondaryUser" product="tv" msgid="2293713284515865200">"Briše podatke ovog korisnika na ovom Android TV uređaju bez upozorenja."</string>
- <string name="policydesc_wipeData_secondaryUser" product="automotive" msgid="4658832487305780879">"Briše podatke ovog profila na ovom sistemu za info-zabavu bez upozorenja."</string>
- <string name="policydesc_wipeData_secondaryUser" product="default" msgid="2788325512167208654">"Briše podatke ovog korisnika na ovom telefonu bez upozorenja."</string>
- <string name="policylab_setGlobalProxy" msgid="215332221188670221">"Podesite globalni proksi server uređaja"</string>
- <string name="policydesc_setGlobalProxy" msgid="7149665222705519604">"Podešava globalni proksi uređaja koji će se koristiti dok su smernice omogućene. Samo vlasnik uređaja može da podesi globalni proksi."</string>
- <string name="policylab_expirePassword" msgid="6015404400532459169">"Podesi istek. lozin. za zaklj. ekr."</string>
- <string name="policydesc_expirePassword" msgid="9136524319325960675">"Menja koliko često lozinka, PIN ili šablon za zaključavanje ekrana mora da se menja."</string>
- <string name="policylab_encryptedStorage" msgid="9012936958126670110">"Podešavanje šifrovanja skladišta"</string>
- <string name="policydesc_encryptedStorage" msgid="1102516950740375617">"Zahteva da sačuvani podaci aplikacije budu šifrovani."</string>
- <string name="policylab_disableCamera" msgid="5749486347810162018">"Onemogućavanje kamera"</string>
- <string name="policydesc_disableCamera" msgid="3204405908799676104">"Sprečite korišćenje svih kamera uređaja."</string>
- <string name="policylab_disableKeyguardFeatures" msgid="5071855750149949741">"Onemogućavanje funkcija zaklj. ekrana"</string>
- <string name="policydesc_disableKeyguardFeatures" msgid="6641673177041195957">"Sprečava korišćenje nekih funkcija zaključavanja ekrana."</string>
+ <string name="face_error_vendor_unknown" msgid="7387005932083302070">"Дошло је до проблема. Пробајте поново."</string>
+ <string name="face_icon_content_description" msgid="465030547475916280">"Икона лица"</string>
+ <string name="permlab_readSyncSettings" msgid="6250532864893156277">"читање подешавања синхронизације"</string>
+ <string name="permdesc_readSyncSettings" msgid="1325658466358779298">"Дозвољава апликацији да чита подешавања синхронизације за налог. На пример, овако може да се утврди да ли је апликација Људи синхронизована са налогом."</string>
+ <string name="permlab_writeSyncSettings" msgid="6583154300780427399">"укључивање и искључивање синхронизације"</string>
+ <string name="permdesc_writeSyncSettings" msgid="6029151549667182687">"Дозвољава апликацији да мења подешавања синхронизације за налог. На пример, овако може да се омогући синхронизација апликације Људи са налогом."</string>
+ <string name="permlab_readSyncStats" msgid="3747407238320105332">"читање статистике о синхронизацији"</string>
+ <string name="permdesc_readSyncStats" msgid="3867809926567379434">"Дозвољава апликацији да чита статистику синхронизације за налог, укључујући историју синхронизованих догађаја и количину података који се синхронизују."</string>
+ <string name="permlab_sdcardRead" msgid="5791467020950064920">"читање садржаја дељеног меморијског простора"</string>
+ <string name="permdesc_sdcardRead" msgid="6872973242228240382">"Дозвољава апликацији да чита садржај дељеног меморијског простора."</string>
+ <string name="permlab_readMediaAudio" msgid="8723513075731763810">"читање аудио фајлова из дељеног меморијског простора"</string>
+ <string name="permdesc_readMediaAudio" msgid="5299772574434619399">"Омогућава апликацији да чита аудио фајлове из дељеног меморијског простора."</string>
+ <string name="permlab_readMediaVideo" msgid="7768003311260655007">"читање видео фајлова из дељеног меморијског простора"</string>
+ <string name="permdesc_readMediaVideo" msgid="3846400073770403528">"Омогућава апликацији да чита видео фајлове из дељеног меморијског простора."</string>
+ <string name="permlab_readMediaImages" msgid="4057590631020986789">"читање фајлова слика из дељеног меморијског простора"</string>
+ <string name="permdesc_readMediaImages" msgid="5836219373138469259">"Омогућава апликацији да чита фајлове слика из дељеног меморијског простора."</string>
+ <string name="permlab_readVisualUserSelect" msgid="5516204215354667586">"читање фајлова слика и видео снимака које корисник бира из дељеног меморијског простора"</string>
+ <string name="permdesc_readVisualUserSelect" msgid="8027174717714968217">"Омогућава апликацији да чита фајлове слика и видео снимака које изаберете из дељеног меморијског простора."</string>
+ <string name="permlab_sdcardWrite" msgid="4863021819671416668">"мењање или брисање садржаја дељеног меморијског простора"</string>
+ <string name="permdesc_sdcardWrite" msgid="8376047679331387102">"Дозвољава апликацији да уписује садржај дељеног меморијског простора."</string>
+ <string name="permlab_use_sip" msgid="8250774565189337477">"упућивање/пријем SIP позива"</string>
+ <string name="permdesc_use_sip" msgid="3590270893253204451">"Омогућава апликацији да упућује и прима SIP позиве."</string>
+ <string name="permlab_register_sim_subscription" msgid="1653054249287576161">"региструје нове везе са телекомуникационим мрежама преко SIM картице"</string>
+ <string name="permdesc_register_sim_subscription" msgid="4183858662792232464">"Дозвољава апликацији да региструје нове везе са телекомуникационим мрежама преко SIM картице."</string>
+ <string name="permlab_register_call_provider" msgid="6135073566140050702">"региструје нове везе са телекомуникационим мрежама"</string>
+ <string name="permdesc_register_call_provider" msgid="4201429251459068613">"Дозвољава апликацији да региструје нове везе са телекомуникационим мрежама."</string>
+ <string name="permlab_connection_manager" msgid="3179365584691166915">"управљање везама са телекомуникационим мрежама"</string>
+ <string name="permdesc_connection_manager" msgid="1426093604238937733">"Дозвољава апликацији да управља везама са телекомуникационим мрежама."</string>
+ <string name="permlab_bind_incall_service" msgid="5990625112603493016">"комуницирај са екраном током позива"</string>
+ <string name="permdesc_bind_incall_service" msgid="4124917526967765162">"Дозвољава апликацији да контролише када и како се кориснику приказује екран током позива."</string>
+ <string name="permlab_bind_connection_service" msgid="5409268245525024736">"да ступа у интеракцију са телефонским услугама"</string>
+ <string name="permdesc_bind_connection_service" msgid="6261796725253264518">"Дозвољава интеракцију апликације са телефонским услугама ради упућивања/примања позива."</string>
+ <string name="permlab_control_incall_experience" msgid="6436863486094352987">"пружај кориснички доживљај током позива"</string>
+ <string name="permdesc_control_incall_experience" msgid="5896723643771737534">"Дозвољава апликацији да пружа кориснички доживљај током позива."</string>
+ <string name="permlab_readNetworkUsageHistory" msgid="8470402862501573795">"чита историју коришћења мреже"</string>
+ <string name="permdesc_readNetworkUsageHistory" msgid="1112962304941637102">"Дозвољава апликацији да чита историју коришћења мреже за посебне мреже и апликације."</string>
+ <string name="permlab_manageNetworkPolicy" msgid="6872549423152175378">"управљање смерницама за мрежу"</string>
+ <string name="permdesc_manageNetworkPolicy" msgid="1865663268764673296">"Дозвољава апликацији да управља смерницама за мрежу и одређује посебна правила за апликацију."</string>
+ <string name="permlab_modifyNetworkAccounting" msgid="7448790834938749041">"измените обрачунавање коришћења мреже"</string>
+ <string name="permdesc_modifyNetworkAccounting" msgid="5076042642247205390">"Дозвољава апликацији да измени начин на који апликације користе мрежу. Не користе је уобичајене апликације."</string>
+ <string name="permlab_accessNotifications" msgid="7130360248191984741">"приступ обавештењима"</string>
+ <string name="permdesc_accessNotifications" msgid="761730149268789668">"Дозвољава апликацији да преузима, испитује и брише обавештења, укључујући она која постављају друге апликације."</string>
+ <string name="permlab_bindNotificationListenerService" msgid="5848096702733262458">"повезивање са услугом монитора обавештења"</string>
+ <string name="permdesc_bindNotificationListenerService" msgid="4970553694467137126">"Дозвољава власнику да се повеже са интерфејсом услуге монитора обавештења највишег нивоа. Уобичајене апликације никада не би требало да је користе."</string>
+ <string name="permlab_bindConditionProviderService" msgid="5245421224814878483">"повежи са услугом добављача услова"</string>
+ <string name="permdesc_bindConditionProviderService" msgid="6106018791256120258">"Дозвољава власнику да се повеже са интерфејсом највишег нивоа услуге добављача услова. Не би требало никада да буде потребно за уобичајене апликације."</string>
+ <string name="permlab_bindDreamService" msgid="4776175992848982706">"повезивање са услугом сањарења"</string>
+ <string name="permdesc_bindDreamService" msgid="9129615743300572973">"Дозвољава власнику да се повеже са интерфејсом услуге сањарења највишег нивоа. Уобичајене апликације никада не би требало да је користе."</string>
+ <string name="permlab_invokeCarrierSetup" msgid="5098810760209818140">"позивање апликације са конфигурацијом коју одређује оператер"</string>
+ <string name="permdesc_invokeCarrierSetup" msgid="4790845896063237887">"Дозвољава власнику да позива апликацију са конфигурацијом коју одређује оператер. Уобичајене апликације никада не би требало да је користе."</string>
+ <string name="permlab_accessNetworkConditions" msgid="1270732533356286514">"праћење података о условима на мрежи"</string>
+ <string name="permdesc_accessNetworkConditions" msgid="2959269186741956109">"Дозвољава апликацији да прати податке о условима на мрежи. Не би никада требало да буде потребно за нормалне апликације."</string>
+ <string name="permlab_setInputCalibration" msgid="932069700285223434">"промени калибрацију улазног уређаја"</string>
+ <string name="permdesc_setInputCalibration" msgid="2937872391426631726">"Дозвољава апликацији да модификује параметре калибрације додирног екрана. Не би требало да буде потребно за нормалне апликације."</string>
+ <string name="permlab_accessDrmCertificates" msgid="6473765454472436597">"приступ DRM сертификатима"</string>
+ <string name="permdesc_accessDrmCertificates" msgid="6983139753493781941">"Дозвољава апликацији да додељује и користи DRM сертификате. Никада не би требало да се користи за уобичајене апликације."</string>
+ <string name="permlab_handoverStatus" msgid="7620438488137057281">"пријем статуса пребацивања помоћу Android пребацивања"</string>
+ <string name="permdesc_handoverStatus" msgid="3842269451732571070">"Дозвољава овој апликацији да прима информације о актуелним пребацивањима помоћу Android пребацивања"</string>
+ <string name="permlab_removeDrmCertificates" msgid="710576248717404416">"уклањај DRM сертификате"</string>
+ <string name="permdesc_removeDrmCertificates" msgid="4068445390318355716">"Дозвољава апликацији да уклања DRM сертификате. Никада не би требало да се користи за обичне апликације."</string>
+ <string name="permlab_bindCarrierMessagingService" msgid="3363450860593096967">"повезивање са услугом за размену порука мобилног оператера"</string>
+ <string name="permdesc_bindCarrierMessagingService" msgid="6316457028173478345">"Дозвољава власнику да се повеже са интерфејсом највишег нивоа за услугу за размену порука мобилног оператера. Никада не би требало да буде потребно за стандардне апликације."</string>
+ <string name="permlab_bindCarrierServices" msgid="2395596978626237474">"повезивање са услугама оператера"</string>
+ <string name="permdesc_bindCarrierServices" msgid="9185614481967262900">"Дозвољава власнику да се повеже са услугама оператера. Никада не би требало да буде потребно за обичне апликације."</string>
+ <string name="permlab_access_notification_policy" msgid="5524112842876975537">"приступај подешавању Не узнемиравај"</string>
+ <string name="permdesc_access_notification_policy" msgid="8538374112403845013">"Дозвољава апликацији да чита и уписује конфигурацију подешавања Не узнемиравај."</string>
+ <string name="permlab_startViewPermissionUsage" msgid="1504564328641112341">"почетак коришћења дозволе за преглед"</string>
+ <string name="permdesc_startViewPermissionUsage" msgid="2820325605959586538">"Дозвољава власнику да започне коришћење дозволе за апликацију. Никада не би требало да буде потребна за уобичајене апликације."</string>
+ <string name="permlab_startReviewPermissionDecisions" msgid="8690578688476599284">"покретање прегледа одлука о дозволама"</string>
+ <string name="permdesc_startReviewPermissionDecisions" msgid="2775556853503004236">"Дозвољава власнику да покрене екран за проверу одлука о дозволама. Никада не би требало да буде потребно за обичне апликације."</string>
+ <string name="permlab_startViewAppFeatures" msgid="7955084203185903001">"покретање приказа функција апликације"</string>
+ <string name="permdesc_startViewAppFeatures" msgid="7207240860165206107">"Дозвољава носиоцу дозволе да започне прегледање информација о функцијама апликације."</string>
+ <string name="permlab_highSamplingRateSensors" msgid="3941068435726317070">"приступ подацима сензора при великој брзини узорковања"</string>
+ <string name="permdesc_highSamplingRateSensors" msgid="8430061978931155995">"Дозвољава апликацији да узима узорак података сензора при брзини већој од 200 Hz"</string>
+ <string name="policylab_limitPassword" msgid="4851829918814422199">"Подешавање правила за лозинку"</string>
+ <string name="policydesc_limitPassword" msgid="4105491021115793793">"Контролише дужину и знакове дозвољене у лозинкама и PIN-овима за закључавање екрана."</string>
+ <string name="policylab_watchLogin" msgid="7599669460083719504">"Надгледајте покушаје откључавања екрана"</string>
+ <string name="policydesc_watchLogin" product="tablet" msgid="2388436408621909298">"Прати број нетачно унетих лозинки приликом откључавања екрана и закључава таблет или брише податке са таблета ако је нетачна лозинка унета превише пута."</string>
+ <string name="policydesc_watchLogin" product="tv" msgid="2140588224468517507">"Надгледа број нетачних лозинки унетих при откључавању екрана и закључава Android TV уређај или брише све податке са Android TV уређаја ако се унесе превише нетачних лозинки."</string>
+ <string name="policydesc_watchLogin" product="automotive" msgid="7011438994051251521">"Прати број нетачно унетих лозинки при откључавању екрана и закључава систем за инфо-забаву или брише све податке са система за инфо-забаву ако је нетачна лозинка унета превише пута."</string>
+ <string name="policydesc_watchLogin" product="default" msgid="4885030206253600299">"Прати број нетачно унетих лозинки при откључавању екрана и закључава телефон или брише све податке са телефона ако је нетачна лозинка унета превише пута."</string>
+ <string name="policydesc_watchLogin_secondaryUser" product="tablet" msgid="2049038943004297474">"Надгледа број нетачних лозинки унетих при откључавању екрана и закључава таблет или брише све податке овог корисника ако се унесе превише нетачних лозинки."</string>
+ <string name="policydesc_watchLogin_secondaryUser" product="tv" msgid="8965224107449407052">"Надгледа број нетачних лозинки унетих при откључавању екрана и закључава Android TV уређај или брише све податке овог корисника ако се унесе превише нетачних лозинки."</string>
+ <string name="policydesc_watchLogin_secondaryUser" product="automotive" msgid="7180857406058327941">"Надгледа број нетачних лозинки унетих при откључавању екрана и закључава систем за инфо-забаву или брише све податке овог профила ако се унесе превише нетачних лозинки."</string>
+ <string name="policydesc_watchLogin_secondaryUser" product="default" msgid="9177645136475155924">"Надгледа број нетачних лозинки унетих при откључавању екрана и закључава телефон или брише све податке овог корисника ако се унесе превише нетачних лозинки."</string>
+ <string name="policylab_resetPassword" msgid="214556238645096520">"Промена закључавања екрана"</string>
+ <string name="policydesc_resetPassword" msgid="4626419138439341851">"Мења закључавање екрана."</string>
+ <string name="policylab_forceLock" msgid="7360335502968476434">"Закључавање екрана"</string>
+ <string name="policydesc_forceLock" msgid="1008844760853899693">"Контрола начина и времена закључавања екрана."</string>
+ <string name="policylab_wipeData" msgid="1359485247727537311">"Брисање свих података"</string>
+ <string name="policydesc_wipeData" product="tablet" msgid="7245372676261947507">"Брисање података на таблету без упозорења ресетовањем на фабричка подешавања."</string>
+ <string name="policydesc_wipeData" product="tv" msgid="513862488950801261">"Брише податке Android TV уређаја без упозорења помоћу ресетовања на фабричка подешавања."</string>
+ <string name="policydesc_wipeData" product="automotive" msgid="660804547737323300">"Брише податке на систему за инфо-забаву без упозорења ресетовањем на фабричка подешавања."</string>
+ <string name="policydesc_wipeData" product="default" msgid="8036084184768379022">"Брисање података на телефону без упозорења ресетовањем на фабричка подешавања."</string>
+ <string name="policylab_wipeData_secondaryUser" product="automotive" msgid="115034358520328373">"Брисање података профила"</string>
+ <string name="policylab_wipeData_secondaryUser" product="default" msgid="413813645323433166">"Обриши податке корисника"</string>
+ <string name="policydesc_wipeData_secondaryUser" product="tablet" msgid="2336676480090926470">"Брише податке овог корисника на овом таблету без упозорења."</string>
+ <string name="policydesc_wipeData_secondaryUser" product="tv" msgid="2293713284515865200">"Брише податке овог корисника на овом Android TV уређају без упозорења."</string>
+ <string name="policydesc_wipeData_secondaryUser" product="automotive" msgid="4658832487305780879">"Брише податке овог профила на овом систему за инфо-забаву без упозорења."</string>
+ <string name="policydesc_wipeData_secondaryUser" product="default" msgid="2788325512167208654">"Брише податке овог корисника на овом телефону без упозорења."</string>
+ <string name="policylab_setGlobalProxy" msgid="215332221188670221">"Подесите глобални прокси сервер уређаја"</string>
+ <string name="policydesc_setGlobalProxy" msgid="7149665222705519604">"Подешава глобални прокси уређаја који ће се користити док су смернице омогућене. Само власник уређаја може да подеси глобални прокси."</string>
+ <string name="policylab_expirePassword" msgid="6015404400532459169">"Подеси истек. лозин. за закљ. екр."</string>
+ <string name="policydesc_expirePassword" msgid="9136524319325960675">"Мења колико често лозинка, PIN или шаблон за закључавање екрана мора да се мења."</string>
+ <string name="policylab_encryptedStorage" msgid="9012936958126670110">"Подешавање шифровања складишта"</string>
+ <string name="policydesc_encryptedStorage" msgid="1102516950740375617">"Захтева да сачувани подаци апликације буду шифровани."</string>
+ <string name="policylab_disableCamera" msgid="5749486347810162018">"Онемогућавање камера"</string>
+ <string name="policydesc_disableCamera" msgid="3204405908799676104">"Спречите коришћење свих камера уређаја."</string>
+ <string name="policylab_disableKeyguardFeatures" msgid="5071855750149949741">"Онемогућавање функција закљ. екрана"</string>
+ <string name="policydesc_disableKeyguardFeatures" msgid="6641673177041195957">"Спречава коришћење неких функција закључавања екрана."</string>
<string-array name="phoneTypes">
- <item msgid="8996339953292723951">"Kuća"</item>
- <item msgid="7740243458912727194">"Mobilni"</item>
- <item msgid="8526146065496663766">"Posao"</item>
- <item msgid="8150904584178569699">"Faks na poslu"</item>
- <item msgid="4537253139152229577">"Faks kod kuće"</item>
- <item msgid="6751245029698664340">"Pejdžer"</item>
- <item msgid="1692790665884224905">"Drugo"</item>
- <item msgid="6216981255272016212">"Prilagođeno"</item>
+ <item msgid="8996339953292723951">"Кућа"</item>
+ <item msgid="7740243458912727194">"Мобилни"</item>
+ <item msgid="8526146065496663766">"Посао"</item>
+ <item msgid="8150904584178569699">"Факс на послу"</item>
+ <item msgid="4537253139152229577">"Факс код куће"</item>
+ <item msgid="6751245029698664340">"Пејџер"</item>
+ <item msgid="1692790665884224905">"Друго"</item>
+ <item msgid="6216981255272016212">"Прилагођено"</item>
</string-array>
<string-array name="emailAddressTypes">
- <item msgid="7786349763648997741">"Kuća"</item>
- <item msgid="435564470865989199">"Posao"</item>
- <item msgid="4199433197875490373">"Drugo"</item>
- <item msgid="3233938986670468328">"Prilagođeno"</item>
+ <item msgid="7786349763648997741">"Кућа"</item>
+ <item msgid="435564470865989199">"Посао"</item>
+ <item msgid="4199433197875490373">"Друго"</item>
+ <item msgid="3233938986670468328">"Прилагођено"</item>
</string-array>
<string-array name="postalAddressTypes">
- <item msgid="3861463339764243038">"Kuća"</item>
- <item msgid="5472578890164979109">"Posao"</item>
- <item msgid="5718921296646594739">"Drugo"</item>
- <item msgid="5523122236731783179">"Prilagođeno"</item>
+ <item msgid="3861463339764243038">"Кућа"</item>
+ <item msgid="5472578890164979109">"Посао"</item>
+ <item msgid="5718921296646594739">"Друго"</item>
+ <item msgid="5523122236731783179">"Прилагођено"</item>
</string-array>
<string-array name="imAddressTypes">
- <item msgid="588088543406993772">"Kuća"</item>
- <item msgid="5503060422020476757">"Posao"</item>
- <item msgid="2530391194653760297">"Drugo"</item>
- <item msgid="7640927178025203330">"Prilagođeno"</item>
+ <item msgid="588088543406993772">"Кућа"</item>
+ <item msgid="5503060422020476757">"Посао"</item>
+ <item msgid="2530391194653760297">"Друго"</item>
+ <item msgid="7640927178025203330">"Прилагођено"</item>
</string-array>
<string-array name="organizationTypes">
- <item msgid="6144047813304847762">"Posao"</item>
- <item msgid="7402720230065674193">"Drugo"</item>
- <item msgid="808230403067569648">"Prilagođeno"</item>
+ <item msgid="6144047813304847762">"Посао"</item>
+ <item msgid="7402720230065674193">"Друго"</item>
+ <item msgid="808230403067569648">"Прилагођено"</item>
</string-array>
<string-array name="imProtocols">
<item msgid="7535761744432206400">"AIM"</item>
@@ -871,45 +871,45 @@
<item msgid="4717545739447438044">"ICQ"</item>
<item msgid="8293711853624033835">"Jabber"</item>
</string-array>
- <string name="phoneTypeCustom" msgid="5120365721260686814">"Prilagođeno"</string>
- <string name="phoneTypeHome" msgid="3880132427643623588">"Kuća"</string>
- <string name="phoneTypeMobile" msgid="1178852541462086735">"Mobilni"</string>
- <string name="phoneTypeWork" msgid="6604967163358864607">"Posao"</string>
- <string name="phoneTypeFaxWork" msgid="6757519896109439123">"Faks na poslu"</string>
- <string name="phoneTypeFaxHome" msgid="6678559953115904345">"Faks kod kuće"</string>
- <string name="phoneTypePager" msgid="576402072263522767">"Pejdžer"</string>
- <string name="phoneTypeOther" msgid="6918196243648754715">"Drugo"</string>
- <string name="phoneTypeCallback" msgid="3455781500844157767">"Povratni poziv"</string>
- <string name="phoneTypeCar" msgid="4604775148963129195">"Automobil"</string>
- <string name="phoneTypeCompanyMain" msgid="4482773154536455441">"Poslovni glavni"</string>
+ <string name="phoneTypeCustom" msgid="5120365721260686814">"Прилагођено"</string>
+ <string name="phoneTypeHome" msgid="3880132427643623588">"Кућа"</string>
+ <string name="phoneTypeMobile" msgid="1178852541462086735">"Мобилни"</string>
+ <string name="phoneTypeWork" msgid="6604967163358864607">"Посао"</string>
+ <string name="phoneTypeFaxWork" msgid="6757519896109439123">"Факс на послу"</string>
+ <string name="phoneTypeFaxHome" msgid="6678559953115904345">"Факс код куће"</string>
+ <string name="phoneTypePager" msgid="576402072263522767">"Пејџер"</string>
+ <string name="phoneTypeOther" msgid="6918196243648754715">"Друго"</string>
+ <string name="phoneTypeCallback" msgid="3455781500844157767">"Повратни позив"</string>
+ <string name="phoneTypeCar" msgid="4604775148963129195">"Аутомобил"</string>
+ <string name="phoneTypeCompanyMain" msgid="4482773154536455441">"Пословни главни"</string>
<string name="phoneTypeIsdn" msgid="2496238954533998512">"ISDN"</string>
- <string name="phoneTypeMain" msgid="5199722006991000111">"Glavni"</string>
- <string name="phoneTypeOtherFax" msgid="3037145630364770357">"Drugi faks"</string>
- <string name="phoneTypeRadio" msgid="2637819130239264771">"Radio"</string>
- <string name="phoneTypeTelex" msgid="2558783611711876562">"Teleks"</string>
+ <string name="phoneTypeMain" msgid="5199722006991000111">"Главни"</string>
+ <string name="phoneTypeOtherFax" msgid="3037145630364770357">"Други факс"</string>
+ <string name="phoneTypeRadio" msgid="2637819130239264771">"Радио"</string>
+ <string name="phoneTypeTelex" msgid="2558783611711876562">"Телекс"</string>
<string name="phoneTypeTtyTdd" msgid="532038552105328779">"TTY TDD"</string>
- <string name="phoneTypeWorkMobile" msgid="7522314392003565121">"Poslovni mobilni"</string>
- <string name="phoneTypeWorkPager" msgid="3748332310638505234">"Poslovni pejdžer"</string>
- <string name="phoneTypeAssistant" msgid="757550783842231039">"Pomoćnik"</string>
+ <string name="phoneTypeWorkMobile" msgid="7522314392003565121">"Пословни мобилни"</string>
+ <string name="phoneTypeWorkPager" msgid="3748332310638505234">"Пословни пејџер"</string>
+ <string name="phoneTypeAssistant" msgid="757550783842231039">"Помоћник"</string>
<string name="phoneTypeMms" msgid="1799747455131365989">"MMS"</string>
- <string name="eventTypeCustom" msgid="3257367158986466481">"Prilagođeno"</string>
- <string name="eventTypeBirthday" msgid="7770026752793912283">"Rođendan"</string>
- <string name="eventTypeAnniversary" msgid="4684702412407916888">"Godišnjica"</string>
- <string name="eventTypeOther" msgid="530671238533887997">"Drugi"</string>
- <string name="emailTypeCustom" msgid="1809435350482181786">"Prilagođeno"</string>
- <string name="emailTypeHome" msgid="1597116303154775999">"Kuća"</string>
- <string name="emailTypeWork" msgid="2020095414401882111">"Posao"</string>
- <string name="emailTypeOther" msgid="5131130857030897465">"Drugo"</string>
- <string name="emailTypeMobile" msgid="787155077375364230">"Mobilni"</string>
- <string name="postalTypeCustom" msgid="5645590470242939129">"Prilagođeno"</string>
- <string name="postalTypeHome" msgid="7562272480949727912">"Kuća"</string>
- <string name="postalTypeWork" msgid="8553425424652012826">"Posao"</string>
- <string name="postalTypeOther" msgid="7094245413678857420">"Drugo"</string>
- <string name="imTypeCustom" msgid="5653384545085765570">"Prilagođeno"</string>
- <string name="imTypeHome" msgid="6996507981044278216">"Kuća"</string>
- <string name="imTypeWork" msgid="2099668940169903123">"Posao"</string>
- <string name="imTypeOther" msgid="8068447383276219810">"Drugo"</string>
- <string name="imProtocolCustom" msgid="4437878287653764692">"Prilagođeno"</string>
+ <string name="eventTypeCustom" msgid="3257367158986466481">"Прилагођено"</string>
+ <string name="eventTypeBirthday" msgid="7770026752793912283">"Рођендан"</string>
+ <string name="eventTypeAnniversary" msgid="4684702412407916888">"Годишњица"</string>
+ <string name="eventTypeOther" msgid="530671238533887997">"Други"</string>
+ <string name="emailTypeCustom" msgid="1809435350482181786">"Прилагођено"</string>
+ <string name="emailTypeHome" msgid="1597116303154775999">"Кућа"</string>
+ <string name="emailTypeWork" msgid="2020095414401882111">"Посао"</string>
+ <string name="emailTypeOther" msgid="5131130857030897465">"Друго"</string>
+ <string name="emailTypeMobile" msgid="787155077375364230">"Мобилни"</string>
+ <string name="postalTypeCustom" msgid="5645590470242939129">"Прилагођено"</string>
+ <string name="postalTypeHome" msgid="7562272480949727912">"Кућа"</string>
+ <string name="postalTypeWork" msgid="8553425424652012826">"Посао"</string>
+ <string name="postalTypeOther" msgid="7094245413678857420">"Друго"</string>
+ <string name="imTypeCustom" msgid="5653384545085765570">"Прилагођено"</string>
+ <string name="imTypeHome" msgid="6996507981044278216">"Кућа"</string>
+ <string name="imTypeWork" msgid="2099668940169903123">"Посао"</string>
+ <string name="imTypeOther" msgid="8068447383276219810">"Друго"</string>
+ <string name="imProtocolCustom" msgid="4437878287653764692">"Прилагођено"</string>
<string name="imProtocolAim" msgid="4050198236506604378">"AIM"</string>
<string name="imProtocolMsn" msgid="2257148557766499232">"Windows Live"</string>
<string name="imProtocolYahoo" msgid="5373338758093392231">"Yahoo"</string>
@@ -919,51 +919,51 @@
<string name="imProtocolIcq" msgid="2410325380427389521">"ICQ"</string>
<string name="imProtocolJabber" msgid="7919269388889582015">"Jabber"</string>
<string name="imProtocolNetMeeting" msgid="4985002408136148256">"NetMeeting"</string>
- <string name="orgTypeWork" msgid="8684458700669564172">"Posao"</string>
- <string name="orgTypeOther" msgid="5450675258408005553">"Drugo"</string>
- <string name="orgTypeCustom" msgid="1126322047677329218">"Prilagođeno"</string>
- <string name="relationTypeCustom" msgid="282938315217441351">"Prilagođeno"</string>
- <string name="relationTypeAssistant" msgid="4057605157116589315">"Pomoćnik"</string>
- <string name="relationTypeBrother" msgid="7141662427379247820">"Brat"</string>
- <string name="relationTypeChild" msgid="9076258911292693601">"Dete"</string>
- <string name="relationTypeDomesticPartner" msgid="7825306887697559238">"Nevenčani partner"</string>
- <string name="relationTypeFather" msgid="3856225062864790596">"Otac"</string>
- <string name="relationTypeFriend" msgid="3192092625893980574">"Prijatelj"</string>
- <string name="relationTypeManager" msgid="2272860813153171857">"Menadžer"</string>
- <string name="relationTypeMother" msgid="2331762740982699460">"Majka"</string>
- <string name="relationTypeParent" msgid="4177920938333039882">"Roditelj"</string>
- <string name="relationTypePartner" msgid="4018017075116766194">"Partner"</string>
- <string name="relationTypeReferredBy" msgid="5285082289602849400">"Uputio/la"</string>
- <string name="relationTypeRelative" msgid="3396498519818009134">"Rođak"</string>
- <string name="relationTypeSister" msgid="3721676005094140671">"Sestra"</string>
- <string name="relationTypeSpouse" msgid="6916682664436031703">"Suprug/a"</string>
- <string name="sipAddressTypeCustom" msgid="6283889809842649336">"Prilagođeno"</string>
- <string name="sipAddressTypeHome" msgid="5918441930656878367">"Početna"</string>
- <string name="sipAddressTypeWork" msgid="7873967986701216770">"Posao"</string>
- <string name="sipAddressTypeOther" msgid="6317012577345187275">"Drugi"</string>
- <string name="quick_contacts_not_available" msgid="1262709196045052223">"Nije pronađena nijedna aplikacija za prikaz ovog kontakta."</string>
- <string name="keyguard_password_enter_pin_code" msgid="6401406801060956153">"Unesite PIN kôd"</string>
- <string name="keyguard_password_enter_puk_code" msgid="3112256684547584093">"Unesite PUK i novi PIN kôd"</string>
- <string name="keyguard_password_enter_puk_prompt" msgid="2825313071899938305">"PUK kôd"</string>
- <string name="keyguard_password_enter_pin_prompt" msgid="5505434724229581207">"Novi PIN kôd"</string>
- <string name="keyguard_password_entry_touch_hint" msgid="4032288032993261520"><font size="17">"Dodirnite za unos lozinke"</font></string>
- <string name="keyguard_password_enter_password_code" msgid="2751130557661643482">"Otkucajte lozinku da biste otključali"</string>
- <string name="keyguard_password_enter_pin_password_code" msgid="7792964196473964340">"Unesite PIN za otključavanje"</string>
- <string name="keyguard_password_wrong_pin_code" msgid="8583732939138432793">"PIN kôd je netačan."</string>
- <string name="keyguard_label_text" msgid="3841953694564168384">"Da biste otključali, pritisnite „Meni“, a zatim 0."</string>
- <string name="emergency_call_dialog_number_for_display" msgid="2978165477085612673">"Broj za hitne slučajeve"</string>
- <string name="lockscreen_carrier_default" msgid="6192313772955399160">"Mobilna mreža nije dostupna"</string>
- <string name="lockscreen_screen_locked" msgid="7364905540516041817">"Ekran je zaključan."</string>
- <string name="lockscreen_instructions_when_pattern_enabled" msgid="7982445492532123308">"Pritisnite „Meni“ da biste otključali telefon ili uputite hitan poziv."</string>
- <string name="lockscreen_instructions_when_pattern_disabled" msgid="7434061749374801753">"Pritisnite „Meni“ za otključavanje."</string>
- <string name="lockscreen_pattern_instructions" msgid="3169991838169244941">"Unesite šablon za otključavanje"</string>
- <string name="lockscreen_emergency_call" msgid="7500692654885445299">"Hitne službe"</string>
- <string name="lockscreen_return_to_call" msgid="3156883574692006382">"Nazad na poziv"</string>
- <string name="lockscreen_pattern_correct" msgid="8050630103651508582">"Tačno!"</string>
- <string name="lockscreen_pattern_wrong" msgid="2940138714468358458">"Probajte ponovo"</string>
- <string name="lockscreen_password_wrong" msgid="8605355913868947490">"Probajte ponovo"</string>
- <string name="lockscreen_storage_locked" msgid="634993789186443380">"Otključaj za sve funkcije i podatke"</string>
- <string name="faceunlock_multiple_failures" msgid="681991538434031708">"Premašen je najveći dozvoljeni broj pokušaja Otključavanja licem"</string>
+ <string name="orgTypeWork" msgid="8684458700669564172">"Посао"</string>
+ <string name="orgTypeOther" msgid="5450675258408005553">"Друго"</string>
+ <string name="orgTypeCustom" msgid="1126322047677329218">"Прилагођено"</string>
+ <string name="relationTypeCustom" msgid="282938315217441351">"Прилагођено"</string>
+ <string name="relationTypeAssistant" msgid="4057605157116589315">"Помоћник"</string>
+ <string name="relationTypeBrother" msgid="7141662427379247820">"Брат"</string>
+ <string name="relationTypeChild" msgid="9076258911292693601">"Дете"</string>
+ <string name="relationTypeDomesticPartner" msgid="7825306887697559238">"Невенчани партнер"</string>
+ <string name="relationTypeFather" msgid="3856225062864790596">"Отац"</string>
+ <string name="relationTypeFriend" msgid="3192092625893980574">"Пријатељ"</string>
+ <string name="relationTypeManager" msgid="2272860813153171857">"Менаџер"</string>
+ <string name="relationTypeMother" msgid="2331762740982699460">"Мајка"</string>
+ <string name="relationTypeParent" msgid="4177920938333039882">"Родитељ"</string>
+ <string name="relationTypePartner" msgid="4018017075116766194">"Партнер"</string>
+ <string name="relationTypeReferredBy" msgid="5285082289602849400">"Упутио/ла"</string>
+ <string name="relationTypeRelative" msgid="3396498519818009134">"Рођак"</string>
+ <string name="relationTypeSister" msgid="3721676005094140671">"Сестра"</string>
+ <string name="relationTypeSpouse" msgid="6916682664436031703">"Супруг/а"</string>
+ <string name="sipAddressTypeCustom" msgid="6283889809842649336">"Прилагођено"</string>
+ <string name="sipAddressTypeHome" msgid="5918441930656878367">"Почетна"</string>
+ <string name="sipAddressTypeWork" msgid="7873967986701216770">"Посао"</string>
+ <string name="sipAddressTypeOther" msgid="6317012577345187275">"Други"</string>
+ <string name="quick_contacts_not_available" msgid="1262709196045052223">"Није пронађена ниједна апликација за приказ овог контакта."</string>
+ <string name="keyguard_password_enter_pin_code" msgid="6401406801060956153">"Унесите PIN кôд"</string>
+ <string name="keyguard_password_enter_puk_code" msgid="3112256684547584093">"Унесите PUK и нови PIN кôд"</string>
+ <string name="keyguard_password_enter_puk_prompt" msgid="2825313071899938305">"PUK кôд"</string>
+ <string name="keyguard_password_enter_pin_prompt" msgid="5505434724229581207">"Нови PIN кôд"</string>
+ <string name="keyguard_password_entry_touch_hint" msgid="4032288032993261520"><font size="17">"Додирните за унос лозинке"</font></string>
+ <string name="keyguard_password_enter_password_code" msgid="2751130557661643482">"Откуцајте лозинку да бисте откључали"</string>
+ <string name="keyguard_password_enter_pin_password_code" msgid="7792964196473964340">"Унесите PIN за откључавање"</string>
+ <string name="keyguard_password_wrong_pin_code" msgid="8583732939138432793">"PIN кôд је нетачан."</string>
+ <string name="keyguard_label_text" msgid="3841953694564168384">"Да бисте откључали, притисните „Мени“, а затим 0."</string>
+ <string name="emergency_call_dialog_number_for_display" msgid="2978165477085612673">"Број за хитне случајеве"</string>
+ <string name="lockscreen_carrier_default" msgid="6192313772955399160">"Мобилна мрежа није доступна"</string>
+ <string name="lockscreen_screen_locked" msgid="7364905540516041817">"Екран је закључан."</string>
+ <string name="lockscreen_instructions_when_pattern_enabled" msgid="7982445492532123308">"Притисните „Мени“ да бисте откључали телефон или упутите хитан позив."</string>
+ <string name="lockscreen_instructions_when_pattern_disabled" msgid="7434061749374801753">"Притисните „Мени“ за откључавање."</string>
+ <string name="lockscreen_pattern_instructions" msgid="3169991838169244941">"Унесите шаблон за откључавање"</string>
+ <string name="lockscreen_emergency_call" msgid="7500692654885445299">"Хитне службе"</string>
+ <string name="lockscreen_return_to_call" msgid="3156883574692006382">"Назад на позив"</string>
+ <string name="lockscreen_pattern_correct" msgid="8050630103651508582">"Тачно!"</string>
+ <string name="lockscreen_pattern_wrong" msgid="2940138714468358458">"Пробајте поново"</string>
+ <string name="lockscreen_password_wrong" msgid="8605355913868947490">"Пробајте поново"</string>
+ <string name="lockscreen_storage_locked" msgid="634993789186443380">"Откључај за све функције и податке"</string>
+ <string name="faceunlock_multiple_failures" msgid="681991538434031708">"Премашен је највећи дозвољени број покушаја Откључавања лицем"</string>
<!-- no translation found for lockscreen_missing_sim_message_short (1229301273156907613) -->
<skip />
<!-- no translation found for lockscreen_missing_sim_message (3986843848305639161) -->
@@ -980,804 +980,804 @@
<skip />
<!-- no translation found for lockscreen_permanent_disabled_sim_instructions (6902979937802238429) -->
<skip />
- <string name="lockscreen_transport_prev_description" msgid="2879469521751181478">"Prethodna pesma"</string>
- <string name="lockscreen_transport_next_description" msgid="2931509904881099919">"Sledeća pesma"</string>
- <string name="lockscreen_transport_pause_description" msgid="6705284702135372494">"Pauza"</string>
- <string name="lockscreen_transport_play_description" msgid="106868788691652733">"Pusti"</string>
- <string name="lockscreen_transport_stop_description" msgid="1449552232598355348">"Zaustavi"</string>
- <string name="lockscreen_transport_rew_description" msgid="7680106856221622779">"Premotaj unazad"</string>
- <string name="lockscreen_transport_ffw_description" msgid="4763794746640196772">"Premotaj unapred"</string>
- <string name="emergency_calls_only" msgid="3057351206678279851">"Samo hitni pozivi"</string>
- <string name="lockscreen_network_locked_message" msgid="2814046965899249635">"Mreža je zaključana"</string>
+ <string name="lockscreen_transport_prev_description" msgid="2879469521751181478">"Претходна песма"</string>
+ <string name="lockscreen_transport_next_description" msgid="2931509904881099919">"Следећа песма"</string>
+ <string name="lockscreen_transport_pause_description" msgid="6705284702135372494">"Пауза"</string>
+ <string name="lockscreen_transport_play_description" msgid="106868788691652733">"Пусти"</string>
+ <string name="lockscreen_transport_stop_description" msgid="1449552232598355348">"Заустави"</string>
+ <string name="lockscreen_transport_rew_description" msgid="7680106856221622779">"Премотај уназад"</string>
+ <string name="lockscreen_transport_ffw_description" msgid="4763794746640196772">"Премотај унапред"</string>
+ <string name="emergency_calls_only" msgid="3057351206678279851">"Само хитни позиви"</string>
+ <string name="lockscreen_network_locked_message" msgid="2814046965899249635">"Мрежа је закључана"</string>
<!-- no translation found for lockscreen_sim_puk_locked_message (2867953953604224166) -->
<skip />
- <string name="lockscreen_sim_puk_locked_instructions" msgid="5307979043730860995">"Pogledajte Korisnički vodič ili kontaktirajte Korisničku podršku."</string>
+ <string name="lockscreen_sim_puk_locked_instructions" msgid="5307979043730860995">"Погледајте Кориснички водич или контактирајте Корисничку подршку."</string>
<!-- no translation found for lockscreen_sim_locked_message (5911944931911850164) -->
<skip />
<!-- no translation found for lockscreen_sim_unlock_progress_dialog_message (8381565919325410939) -->
<skip />
- <string name="lockscreen_too_many_failed_attempts_dialog_message" msgid="6458790975898594240">"<xliff:g id="NUMBER_0">%1$d</xliff:g> puta ste nepravilno nacrtali šablon za otključavanje. \n\nProbajte ponovo za <xliff:g id="NUMBER_1">%2$d</xliff:g> sekunde/i."</string>
- <string name="lockscreen_too_many_failed_password_attempts_dialog_message" msgid="3118353451602377380">"<xliff:g id="NUMBER_0">%1$d</xliff:g> puta ste pogrešno uneli lozinku. \n\nProbajte ponovo za <xliff:g id="NUMBER_1">%2$d</xliff:g> sekunde/i."</string>
- <string name="lockscreen_too_many_failed_pin_attempts_dialog_message" msgid="2874278239714821984">"<xliff:g id="NUMBER_0">%1$d</xliff:g> puta ste pogrešno uneli PIN. \n\nProbajte ponovo za <xliff:g id="NUMBER_1">%2$d</xliff:g> sekunde/i."</string>
- <string name="lockscreen_failed_attempts_almost_glogin" product="tablet" msgid="3069635524964070596">"<xliff:g id="NUMBER_0">%1$d</xliff:g> puta ste netačno uneli šablon za otključavanje. Nakon još <xliff:g id="NUMBER_1">%2$d</xliff:g> nesupešna(ih) pokušaja, od vas će biti zatraženo da otključate tablet pomoću podataka za prijavljivanje na Google.\n\n Probajte ponovo za <xliff:g id="NUMBER_2">%3$d</xliff:g> sekunde/i."</string>
- <string name="lockscreen_failed_attempts_almost_glogin" product="tv" msgid="6399092175942158529">"Netačno ste nacrtali šablon za otključavanje <xliff:g id="NUMBER_0">%1$d</xliff:g> puta. Ako pogrešno pokušate još puta (<xliff:g id="NUMBER_1">%2$d</xliff:g>), zatražićemo da otključate telefon pomoću Android TV uređaja.\n\n Probajte ponovo za <xliff:g id="NUMBER_2">%3$d</xliff:g> sek."</string>
- <string name="lockscreen_failed_attempts_almost_glogin" product="default" msgid="5691623136957148335">"<xliff:g id="NUMBER_0">%1$d</xliff:g> puta ste netačno uneli šablon za otključavanje. Nakon još <xliff:g id="NUMBER_1">%2$d</xliff:g> nesupešna(ih) pokušaja, od vas će biti zatraženo da otključate telefon pomoću podataka za prijavljivanje na Google.\n\n Probajte ponovo za <xliff:g id="NUMBER_2">%3$d</xliff:g> sekunde/i."</string>
- <string name="lockscreen_failed_attempts_almost_at_wipe" product="tablet" msgid="7914445759242151426">"Nepravilno ste pokušali da otključate tablet <xliff:g id="NUMBER_0">%1$d</xliff:g> puta. Nakon još neuspešnih pokušaja (<xliff:g id="NUMBER_1">%2$d</xliff:g>) tablet će biti resetovan na fabrička podešavanja i svi korisnički podaci će biti izgubljeni."</string>
- <string name="lockscreen_failed_attempts_almost_at_wipe" product="tv" msgid="4275591249631864248">"Broj vaših neuspešnih pokušaja da otključate Android TV uređaj: <xliff:g id="NUMBER_0">%1$d</xliff:g>. Broj preostalih neuspešnih pokušaja posle kojih će se Android TV resetovati na fabrička podešavanja i svi podaci korisnika će biti izgubljeni: <xliff:g id="NUMBER_1">%2$d</xliff:g>."</string>
- <string name="lockscreen_failed_attempts_almost_at_wipe" product="default" msgid="1166532464798446579">"Neispravno ste pokušali da otključate telefon <xliff:g id="NUMBER_0">%1$d</xliff:g> puta. Nakon još neuspešnih pokušaja (<xliff:g id="NUMBER_1">%2$d</xliff:g>) telefon će biti resetovan na fabrička podešavanja i svi korisnički podaci će biti izgubljeni."</string>
- <string name="lockscreen_failed_attempts_now_wiping" product="tablet" msgid="8682445539263683414">"Neispravno ste pokušali da otključate tablet <xliff:g id="NUMBER">%d</xliff:g> puta. Tablet će sada biti vraćen na podrazumevana fabrička podešavanja."</string>
- <string name="lockscreen_failed_attempts_now_wiping" product="tv" msgid="2205435033340091883">"Broj vaših neuspešnih pokušaja da otključate Android TV uređaj: <xliff:g id="NUMBER">%d</xliff:g>. Android TV uređaj će se sada resetovati na fabrička podešavanja."</string>
- <string name="lockscreen_failed_attempts_now_wiping" product="default" msgid="2203704707679895487">"Neispravno ste pokušali da otključate telefon <xliff:g id="NUMBER">%d</xliff:g> puta. Telefon će sada biti vraćen na podrazumevana fabrička podešavanja."</string>
- <string name="lockscreen_too_many_failed_attempts_countdown" msgid="6807200118164539589">"Probajte ponovo za <xliff:g id="NUMBER">%d</xliff:g> sekunde/i."</string>
- <string name="lockscreen_forgot_pattern_button_text" msgid="8362442730606839031">"Zaboravili ste šablon?"</string>
- <string name="lockscreen_glogin_forgot_pattern" msgid="9218940117797602518">"Otključavanje naloga"</string>
- <string name="lockscreen_glogin_too_many_attempts" msgid="3775904917743034195">"Previše pokušaja unosa šablona"</string>
- <string name="lockscreen_glogin_instructions" msgid="4695162942525531700">"Da biste otključali, prijavite se pomoću Google naloga."</string>
- <string name="lockscreen_glogin_username_hint" msgid="6916101478673157045">"Korisničko ime (imejl adresa)"</string>
- <string name="lockscreen_glogin_password_hint" msgid="3031027901286812848">"Lozinka"</string>
- <string name="lockscreen_glogin_submit_button" msgid="3590556636347843733">"Prijavi me"</string>
- <string name="lockscreen_glogin_invalid_input" msgid="4369219936865697679">"Nevažeće korisničko ime ili lozinka."</string>
- <string name="lockscreen_glogin_account_recovery_hint" msgid="1683405808525090649">"Zaboravili ste korisničko ime ili lozinku?\nPosetite adresu "<b>"google.com/accounts/recovery"</b>"."</string>
- <string name="lockscreen_glogin_checking_password" msgid="2607271802803381645">"Proveravanje..."</string>
- <string name="lockscreen_unlock_label" msgid="4648257878373307582">"Otključaj"</string>
- <string name="lockscreen_sound_on_label" msgid="1660281470535492430">"Uključi zvuk"</string>
- <string name="lockscreen_sound_off_label" msgid="2331496559245450053">"Isključi zvuk"</string>
- <string name="lockscreen_access_pattern_start" msgid="3778502525702613399">"Obrazac je započet"</string>
- <string name="lockscreen_access_pattern_cleared" msgid="7493849102641167049">"Obrazac je obrisan"</string>
- <string name="lockscreen_access_pattern_cell_added" msgid="6746676335293144163">"Ćelija je dodata"</string>
- <string name="lockscreen_access_pattern_cell_added_verbose" msgid="2931364927622563465">"Ćelija <xliff:g id="CELL_INDEX">%1$s</xliff:g> je dodata"</string>
- <string name="lockscreen_access_pattern_detected" msgid="3931150554035194012">"Obrazac je dovršen"</string>
- <string name="lockscreen_access_pattern_area" msgid="1288780416685002841">"Oblast šablona."</string>
- <string name="keyguard_accessibility_widget_changed" msgid="7298011259508200234">"%1$s. Vidžet %2$d od %3$d."</string>
- <string name="keyguard_accessibility_add_widget" msgid="8245795023551343672">"Dodaj vidžet."</string>
- <string name="keyguard_accessibility_widget_empty_slot" msgid="544239307077644480">"Prazno"</string>
- <string name="keyguard_accessibility_unlock_area_expanded" msgid="7768634718706488951">"Oblast otključavanja je proširena."</string>
- <string name="keyguard_accessibility_unlock_area_collapsed" msgid="4729922043778400434">"Oblast otključavanja je skupljena."</string>
- <string name="keyguard_accessibility_widget" msgid="6776892679715699875">"Vidžet <xliff:g id="WIDGET_INDEX">%1$s</xliff:g>."</string>
- <string name="keyguard_accessibility_user_selector" msgid="1466067610235696600">"Izbor korisnika"</string>
- <string name="keyguard_accessibility_status" msgid="6792745049712397237">"Status"</string>
- <string name="keyguard_accessibility_camera" msgid="7862557559464986528">"Kamera"</string>
- <string name="keygaurd_accessibility_media_controls" msgid="2267379779900620614">"Kontrole za medije"</string>
- <string name="keyguard_accessibility_widget_reorder_start" msgid="7066213328912939191">"Započela je promena redosleda vidžeta."</string>
- <string name="keyguard_accessibility_widget_reorder_end" msgid="1083806817600593490">"Promena redosleda vidžeta je završena."</string>
- <string name="keyguard_accessibility_widget_deleted" msgid="1509738950119878705">"Vidžet <xliff:g id="WIDGET_INDEX">%1$s</xliff:g> je izbrisan."</string>
- <string name="keyguard_accessibility_expand_lock_area" msgid="4215280881346033434">"Proširi oblast otključavanja."</string>
- <string name="keyguard_accessibility_slide_unlock" msgid="2968195219692413046">"Otključavanje prevlačenjem."</string>
- <string name="keyguard_accessibility_pattern_unlock" msgid="8669128146589233293">"Otključavanje šablonom."</string>
- <string name="keyguard_accessibility_face_unlock" msgid="4533832120787386728">"Otključavanje licem."</string>
- <string name="keyguard_accessibility_pin_unlock" msgid="4020864007967340068">"Otključavanje PIN-om."</string>
- <string name="keyguard_accessibility_sim_pin_unlock" msgid="4895939120871890557">"Otključava SIM karticu PIN-om."</string>
- <string name="keyguard_accessibility_sim_puk_unlock" msgid="3459003464041899101">"Otključava SIM karticu PUK-om."</string>
- <string name="keyguard_accessibility_password_unlock" msgid="6130186108581153265">"Otključavanje lozinkom."</string>
- <string name="keyguard_accessibility_pattern_area" msgid="1419570880512350689">"Oblast šablona."</string>
- <string name="keyguard_accessibility_slide_area" msgid="4331399051142520176">"Oblast prevlačenja."</string>
+ <string name="lockscreen_too_many_failed_attempts_dialog_message" msgid="6458790975898594240">"<xliff:g id="NUMBER_0">%1$d</xliff:g> пута сте неправилно нацртали шаблон за откључавање. \n\nПробајте поново за <xliff:g id="NUMBER_1">%2$d</xliff:g> секунде/и."</string>
+ <string name="lockscreen_too_many_failed_password_attempts_dialog_message" msgid="3118353451602377380">"<xliff:g id="NUMBER_0">%1$d</xliff:g> пута сте погрешно унели лозинку. \n\nПробајте поново за <xliff:g id="NUMBER_1">%2$d</xliff:g> секунде/и."</string>
+ <string name="lockscreen_too_many_failed_pin_attempts_dialog_message" msgid="2874278239714821984">"<xliff:g id="NUMBER_0">%1$d</xliff:g> пута сте погрешно унели PIN. \n\nПробајте поново за <xliff:g id="NUMBER_1">%2$d</xliff:g> секунде/и."</string>
+ <string name="lockscreen_failed_attempts_almost_glogin" product="tablet" msgid="3069635524964070596">"<xliff:g id="NUMBER_0">%1$d</xliff:g> пута сте нетачно унели шаблон за откључавање. Након још <xliff:g id="NUMBER_1">%2$d</xliff:g> несупешна(их) покушаја, од вас ће бити затражено да откључате таблет помоћу података за пријављивање на Google.\n\n Пробајте поново за <xliff:g id="NUMBER_2">%3$d</xliff:g> секунде/и."</string>
+ <string name="lockscreen_failed_attempts_almost_glogin" product="tv" msgid="6399092175942158529">"Нетачно сте нацртали шаблон за откључавање <xliff:g id="NUMBER_0">%1$d</xliff:g> пута. Ако погрешно покушате још пута (<xliff:g id="NUMBER_1">%2$d</xliff:g>), затражићемо да откључате телефон помоћу Android TV уређаја.\n\n Пробајте поново за <xliff:g id="NUMBER_2">%3$d</xliff:g> сек."</string>
+ <string name="lockscreen_failed_attempts_almost_glogin" product="default" msgid="5691623136957148335">"<xliff:g id="NUMBER_0">%1$d</xliff:g> пута сте нетачно унели шаблон за откључавање. Након још <xliff:g id="NUMBER_1">%2$d</xliff:g> несупешна(их) покушаја, од вас ће бити затражено да откључате телефон помоћу података за пријављивање на Google.\n\n Пробајте поново за <xliff:g id="NUMBER_2">%3$d</xliff:g> секунде/и."</string>
+ <string name="lockscreen_failed_attempts_almost_at_wipe" product="tablet" msgid="7914445759242151426">"Неправилно сте покушали да откључате таблет <xliff:g id="NUMBER_0">%1$d</xliff:g> пута. Након још неуспешних покушаја (<xliff:g id="NUMBER_1">%2$d</xliff:g>) таблет ће бити ресетован на фабричка подешавања и сви кориснички подаци ће бити изгубљени."</string>
+ <string name="lockscreen_failed_attempts_almost_at_wipe" product="tv" msgid="4275591249631864248">"Број ваших неуспешних покушаја да откључате Android TV уређај: <xliff:g id="NUMBER_0">%1$d</xliff:g>. Број преосталих неуспешних покушаја после којих ће се Android TV ресетовати на фабричка подешавања и сви подаци корисника ће бити изгубљени: <xliff:g id="NUMBER_1">%2$d</xliff:g>."</string>
+ <string name="lockscreen_failed_attempts_almost_at_wipe" product="default" msgid="1166532464798446579">"Неисправно сте покушали да откључате телефон <xliff:g id="NUMBER_0">%1$d</xliff:g> пута. Након још неуспешних покушаја (<xliff:g id="NUMBER_1">%2$d</xliff:g>) телефон ће бити ресетован на фабричка подешавања и сви кориснички подаци ће бити изгубљени."</string>
+ <string name="lockscreen_failed_attempts_now_wiping" product="tablet" msgid="8682445539263683414">"Неисправно сте покушали да откључате таблет <xliff:g id="NUMBER">%d</xliff:g> пута. Таблет ће сада бити враћен на подразумевана фабричка подешавања."</string>
+ <string name="lockscreen_failed_attempts_now_wiping" product="tv" msgid="2205435033340091883">"Број ваших неуспешних покушаја да откључате Android TV уређај: <xliff:g id="NUMBER">%d</xliff:g>. Android TV уређај ће се сада ресетовати на фабричка подешавања."</string>
+ <string name="lockscreen_failed_attempts_now_wiping" product="default" msgid="2203704707679895487">"Неисправно сте покушали да откључате телефон <xliff:g id="NUMBER">%d</xliff:g> пута. Телефон ће сада бити враћен на подразумевана фабричка подешавања."</string>
+ <string name="lockscreen_too_many_failed_attempts_countdown" msgid="6807200118164539589">"Пробајте поново за <xliff:g id="NUMBER">%d</xliff:g> секунде/и."</string>
+ <string name="lockscreen_forgot_pattern_button_text" msgid="8362442730606839031">"Заборавили сте шаблон?"</string>
+ <string name="lockscreen_glogin_forgot_pattern" msgid="9218940117797602518">"Откључавање налога"</string>
+ <string name="lockscreen_glogin_too_many_attempts" msgid="3775904917743034195">"Превише покушаја уноса шаблона"</string>
+ <string name="lockscreen_glogin_instructions" msgid="4695162942525531700">"Да бисте откључали, пријавите се помоћу Google налога."</string>
+ <string name="lockscreen_glogin_username_hint" msgid="6916101478673157045">"Корисничко име (имејл адреса)"</string>
+ <string name="lockscreen_glogin_password_hint" msgid="3031027901286812848">"Лозинка"</string>
+ <string name="lockscreen_glogin_submit_button" msgid="3590556636347843733">"Пријави ме"</string>
+ <string name="lockscreen_glogin_invalid_input" msgid="4369219936865697679">"Неважеће корисничко име или лозинка."</string>
+ <string name="lockscreen_glogin_account_recovery_hint" msgid="1683405808525090649">"Заборавили сте корисничко име или лозинку?\nПосетите адресу "<b>"google.com/accounts/recovery"</b>"."</string>
+ <string name="lockscreen_glogin_checking_password" msgid="2607271802803381645">"Проверавање..."</string>
+ <string name="lockscreen_unlock_label" msgid="4648257878373307582">"Откључај"</string>
+ <string name="lockscreen_sound_on_label" msgid="1660281470535492430">"Укључи звук"</string>
+ <string name="lockscreen_sound_off_label" msgid="2331496559245450053">"Искључи звук"</string>
+ <string name="lockscreen_access_pattern_start" msgid="3778502525702613399">"Образац је започет"</string>
+ <string name="lockscreen_access_pattern_cleared" msgid="7493849102641167049">"Образац је обрисан"</string>
+ <string name="lockscreen_access_pattern_cell_added" msgid="6746676335293144163">"Ћелија је додата"</string>
+ <string name="lockscreen_access_pattern_cell_added_verbose" msgid="2931364927622563465">"Ћелија <xliff:g id="CELL_INDEX">%1$s</xliff:g> је додата"</string>
+ <string name="lockscreen_access_pattern_detected" msgid="3931150554035194012">"Образац је довршен"</string>
+ <string name="lockscreen_access_pattern_area" msgid="1288780416685002841">"Област шаблона."</string>
+ <string name="keyguard_accessibility_widget_changed" msgid="7298011259508200234">"%1$s. Виџет %2$d од %3$d."</string>
+ <string name="keyguard_accessibility_add_widget" msgid="8245795023551343672">"Додај виџет."</string>
+ <string name="keyguard_accessibility_widget_empty_slot" msgid="544239307077644480">"Празно"</string>
+ <string name="keyguard_accessibility_unlock_area_expanded" msgid="7768634718706488951">"Област откључавања је проширена."</string>
+ <string name="keyguard_accessibility_unlock_area_collapsed" msgid="4729922043778400434">"Област откључавања је скупљена."</string>
+ <string name="keyguard_accessibility_widget" msgid="6776892679715699875">"Виџет <xliff:g id="WIDGET_INDEX">%1$s</xliff:g>."</string>
+ <string name="keyguard_accessibility_user_selector" msgid="1466067610235696600">"Избор корисника"</string>
+ <string name="keyguard_accessibility_status" msgid="6792745049712397237">"Статус"</string>
+ <string name="keyguard_accessibility_camera" msgid="7862557559464986528">"Камера"</string>
+ <string name="keygaurd_accessibility_media_controls" msgid="2267379779900620614">"Контроле за медије"</string>
+ <string name="keyguard_accessibility_widget_reorder_start" msgid="7066213328912939191">"Започела је промена редоследа виџета."</string>
+ <string name="keyguard_accessibility_widget_reorder_end" msgid="1083806817600593490">"Промена редоследа виџета је завршена."</string>
+ <string name="keyguard_accessibility_widget_deleted" msgid="1509738950119878705">"Виџет <xliff:g id="WIDGET_INDEX">%1$s</xliff:g> је избрисан."</string>
+ <string name="keyguard_accessibility_expand_lock_area" msgid="4215280881346033434">"Прошири област откључавања."</string>
+ <string name="keyguard_accessibility_slide_unlock" msgid="2968195219692413046">"Откључавање превлачењем."</string>
+ <string name="keyguard_accessibility_pattern_unlock" msgid="8669128146589233293">"Откључавање шаблоном."</string>
+ <string name="keyguard_accessibility_face_unlock" msgid="4533832120787386728">"Откључавање лицем."</string>
+ <string name="keyguard_accessibility_pin_unlock" msgid="4020864007967340068">"Откључавање PIN-ом."</string>
+ <string name="keyguard_accessibility_sim_pin_unlock" msgid="4895939120871890557">"Откључава SIM картицу PIN-ом."</string>
+ <string name="keyguard_accessibility_sim_puk_unlock" msgid="3459003464041899101">"Откључава SIM картицу PUK-ом."</string>
+ <string name="keyguard_accessibility_password_unlock" msgid="6130186108581153265">"Откључавање лозинком."</string>
+ <string name="keyguard_accessibility_pattern_area" msgid="1419570880512350689">"Област шаблона."</string>
+ <string name="keyguard_accessibility_slide_area" msgid="4331399051142520176">"Област превлачења."</string>
<string name="password_keyboard_label_symbol_key" msgid="2716255580853511949">"?123"</string>
<string name="password_keyboard_label_alpha_key" msgid="5294837425652726684">"ABC"</string>
<string name="password_keyboard_label_alt_key" msgid="8528261816395508841">"ALT"</string>
- <string name="granularity_label_character" msgid="8903387663153706317">"znak"</string>
- <string name="granularity_label_word" msgid="3686589158760620518">"reč"</string>
- <string name="granularity_label_link" msgid="9007852307112046526">"link"</string>
- <string name="granularity_label_line" msgid="376204904280620221">"red"</string>
- <string name="factorytest_failed" msgid="3190979160945298006">"Fabričko testiranje nije uspelo"</string>
- <string name="factorytest_not_system" msgid="5658160199925519869">"Radnja FACTORY_TEST je podržana samo za pakete instalirane u folderu /system/app."</string>
- <string name="factorytest_no_action" msgid="339252838115675515">"Nije pronađen nijedan paket koji obezbeđuje radnju FACTORY_TEST."</string>
- <string name="factorytest_reboot" msgid="2050147445567257365">"Restartuj"</string>
- <string name="js_dialog_title" msgid="7464775045615023241">"Na stranici na adresi „<xliff:g id="TITLE">%s</xliff:g>“ piše:"</string>
+ <string name="granularity_label_character" msgid="8903387663153706317">"знак"</string>
+ <string name="granularity_label_word" msgid="3686589158760620518">"реч"</string>
+ <string name="granularity_label_link" msgid="9007852307112046526">"линк"</string>
+ <string name="granularity_label_line" msgid="376204904280620221">"ред"</string>
+ <string name="factorytest_failed" msgid="3190979160945298006">"Фабричко тестирање није успело"</string>
+ <string name="factorytest_not_system" msgid="5658160199925519869">"Радња FACTORY_TEST је подржана само за пакете инсталиране у фолдеру /system/app."</string>
+ <string name="factorytest_no_action" msgid="339252838115675515">"Није пронађен ниједан пакет који обезбеђује радњу FACTORY_TEST."</string>
+ <string name="factorytest_reboot" msgid="2050147445567257365">"Рестартуј"</string>
+ <string name="js_dialog_title" msgid="7464775045615023241">"На страници на адреси „<xliff:g id="TITLE">%s</xliff:g>“ пише:"</string>
<string name="js_dialog_title_default" msgid="3769524569903332476">"JavaScript"</string>
- <string name="js_dialog_before_unload_title" msgid="7012587995876771246">"Potvrda navigacije"</string>
- <string name="js_dialog_before_unload_positive_button" msgid="4274257182303565509">"Zatvori ovu stranicu"</string>
- <string name="js_dialog_before_unload_negative_button" msgid="3873765747622415310">"Ostani na ovoj stranici"</string>
- <string name="js_dialog_before_unload" msgid="7213364985774778744">"<xliff:g id="MESSAGE">%s</xliff:g>\n\nDa li stvarno želite da napustite ovu stranicu?"</string>
- <string name="save_password_label" msgid="9161712335355510035">"Potvrda"</string>
- <string name="double_tap_toast" msgid="7065519579174882778">"Savet: Dodirnite dvaput da biste uvećali i umanjili prikaz."</string>
- <string name="autofill_this_form" msgid="3187132440451621492">"Autom. pop."</string>
- <string name="setup_autofill" msgid="5431369130866618567">"Podeš. aut. pop."</string>
- <string name="autofill_window_title" msgid="4379134104008111961">"Automatski popunjava <xliff:g id="SERVICENAME">%1$s</xliff:g>"</string>
+ <string name="js_dialog_before_unload_title" msgid="7012587995876771246">"Потврда навигације"</string>
+ <string name="js_dialog_before_unload_positive_button" msgid="4274257182303565509">"Затвори ову страницу"</string>
+ <string name="js_dialog_before_unload_negative_button" msgid="3873765747622415310">"Остани на овој страници"</string>
+ <string name="js_dialog_before_unload" msgid="7213364985774778744">"<xliff:g id="MESSAGE">%s</xliff:g>\n\nДа ли стварно желите да напустите ову страницу?"</string>
+ <string name="save_password_label" msgid="9161712335355510035">"Потврда"</string>
+ <string name="double_tap_toast" msgid="7065519579174882778">"Савет: Додирните двапут да бисте увећали и умањили приказ."</string>
+ <string name="autofill_this_form" msgid="3187132440451621492">"Аутом. поп."</string>
+ <string name="setup_autofill" msgid="5431369130866618567">"Подеш. аут. поп."</string>
+ <string name="autofill_window_title" msgid="4379134104008111961">"Аутоматски попуњава <xliff:g id="SERVICENAME">%1$s</xliff:g>"</string>
<string name="autofill_address_name_separator" msgid="8190155636149596125">" "</string>
<string name="autofill_address_summary_name_format" msgid="3402882515222673691">"$1$2$3"</string>
<string name="autofill_address_summary_separator" msgid="760522655085707045">", "</string>
<string name="autofill_address_summary_format" msgid="8417010069362125194">"$1$2$3"</string>
- <string name="autofill_province" msgid="3676846437741893159">"Pokrajina"</string>
- <string name="autofill_postal_code" msgid="7034789388968295591">"Poštanski broj"</string>
- <string name="autofill_state" msgid="3341725337190434069">"Država"</string>
- <string name="autofill_zip_code" msgid="1315503730274962450">"Poštanski broj"</string>
- <string name="autofill_county" msgid="7781382735643492173">"Okrug"</string>
- <string name="autofill_island" msgid="5367139008536593734">"Ostrvo"</string>
- <string name="autofill_district" msgid="6428712062213557327">"Distrikt"</string>
- <string name="autofill_department" msgid="9047276226873531529">"Odeljenje"</string>
- <string name="autofill_prefecture" msgid="7267397763720241872">"Prefektura"</string>
- <string name="autofill_parish" msgid="6847960518334530198">"Parohija"</string>
- <string name="autofill_area" msgid="8289022370678448983">"Oblast"</string>
- <string name="autofill_emirate" msgid="2544082046790551168">"Emirat"</string>
- <string name="permlab_readHistoryBookmarks" msgid="9102293913842539697">"čitanje veb obeleživača i istorije"</string>
- <string name="permdesc_readHistoryBookmarks" msgid="2323799501008967852">"Dozvoljava aplikaciji da čita istoriju svih URL adresa koje su posećene pomoću Pregledača, kao i sve obeleživače u Pregledaču. Napomena: Ova dozvola se možda na primenjuje na pregledače treće strane i druge aplikacije sa mogućnošću veb pregledanja."</string>
- <string name="permlab_writeHistoryBookmarks" msgid="6090259925187986937">"pisanje veb obeleživača i istorije"</string>
- <string name="permdesc_writeHistoryBookmarks" product="tablet" msgid="573341025292489065">"Dozvoljava aplikaciji da menja istoriju Pregledača ili obeleživače uskladištene na tabletu. Ovo može da omogući aplikaciji da briše ili menja podatke Pregledača. Napomena: Ova dozvola se možda na primenjuje na pregledače treće strane i druge aplikacije sa mogućnošću veb pregledanja."</string>
- <string name="permdesc_writeHistoryBookmarks" product="tv" msgid="88642768580408561">"Dozvoljava aplikaciji da menja istoriju pregledača ili obeleživače koji se čuvaju na Android TV uređaju. Ovo može da omogući aplikaciji da briše ili menja podatke pregledača. Napomena: ova dozvola se možda ne primenjuje na pregledače treće strane ni druge aplikacije sa mogućnostima veb-pregledanja."</string>
- <string name="permdesc_writeHistoryBookmarks" product="default" msgid="2245203087160913652">"Dozvoljava aplikaciji da menja istoriju Pregledača ili obeleživače uskladištene na telefonu. Ovo može da omogući aplikaciji da briše ili menja podatke Pregledača. Napomena: Ova dozvola se možda na primenjuje na pregledače treće strane i druge aplikacije sa mogućnošću veb pregledanja."</string>
- <string name="permlab_setAlarm" msgid="1158001610254173567">"podešavanje alarma"</string>
- <string name="permdesc_setAlarm" msgid="2185033720060109640">"Dozvoljava aplikaciji da podesi alarm u instaliranoj aplikaciji budilnika. Neke aplikacije budilnika možda ne primenjuju ovu funkciju."</string>
- <string name="permlab_addVoicemail" msgid="4770245808840814471">"dodavanje govorne pošte"</string>
- <string name="permdesc_addVoicemail" msgid="5470312139820074324">"Dozvoljava aplikaciji da dodaje poruke u prijemno sanduče govorne pošte."</string>
- <string name="permlab_writeGeolocationPermissions" msgid="8605631647492879449">"izmena dozvola za geografske lokacije Pregledača"</string>
- <string name="permdesc_writeGeolocationPermissions" msgid="5817346421222227772">"Dozvoljava aplikaciji da izmeni dozvole Pregledača za utvrđivanje geografske lokacije. Zlonamerne aplikacije to mogu da zloupotrebe i iskoriste za slanje informacija o lokaciji nasumičnim veb-sajtovima."</string>
- <string name="save_password_message" msgid="2146409467245462965">"Želite li da pregledač zapamti ovu lozinku?"</string>
- <string name="save_password_notnow" msgid="2878327088951240061">"Ne sada"</string>
- <string name="save_password_remember" msgid="6490888932657708341">"Zapamti"</string>
- <string name="save_password_never" msgid="6776808375903410659">"Nikad"</string>
- <string name="open_permission_deny" msgid="5136793905306987251">"Nemate dozvolu da otvorite ovu stranicu."</string>
- <string name="text_copied" msgid="2531420577879738860">"Tekst je kopiran u privremenu memoriju."</string>
- <string name="pasted_from_app" msgid="5627698450808256545">"Aplikacija<xliff:g id="PASTING_APP_NAME">%1$s</xliff:g> je nalepila podatke iz aplikacije <xliff:g id="SOURCE_APP_NAME">%2$s</xliff:g>"</string>
- <string name="pasted_from_clipboard" msgid="7355790625710831847">"<xliff:g id="PASTING_APP_NAME">%1$s</xliff:g> je prelepio/la iz privremene memorije"</string>
- <string name="pasted_text" msgid="4298871641549173733">"Aplikacija<xliff:g id="PASTING_APP_NAME">%1$s</xliff:g> je nalepila tekst koji ste kopirali"</string>
- <string name="pasted_image" msgid="4729097394781491022">"Aplikacija<xliff:g id="PASTING_APP_NAME">%1$s</xliff:g> je nalepila sliku koju ste kopirali"</string>
- <string name="pasted_content" msgid="646276353060777131">"Aplikacija<xliff:g id="PASTING_APP_NAME">%1$s</xliff:g> je nalepila sadržaj koji ste kopirali"</string>
- <string name="more_item_label" msgid="7419249600215749115">"Još"</string>
- <string name="prepend_shortcut_label" msgid="1743716737502867951">"Meni+"</string>
+ <string name="autofill_province" msgid="3676846437741893159">"Покрајина"</string>
+ <string name="autofill_postal_code" msgid="7034789388968295591">"Поштански број"</string>
+ <string name="autofill_state" msgid="3341725337190434069">"Држава"</string>
+ <string name="autofill_zip_code" msgid="1315503730274962450">"Поштански број"</string>
+ <string name="autofill_county" msgid="7781382735643492173">"Округ"</string>
+ <string name="autofill_island" msgid="5367139008536593734">"Острво"</string>
+ <string name="autofill_district" msgid="6428712062213557327">"Дистрикт"</string>
+ <string name="autofill_department" msgid="9047276226873531529">"Одељење"</string>
+ <string name="autofill_prefecture" msgid="7267397763720241872">"Префектура"</string>
+ <string name="autofill_parish" msgid="6847960518334530198">"Парохија"</string>
+ <string name="autofill_area" msgid="8289022370678448983">"Област"</string>
+ <string name="autofill_emirate" msgid="2544082046790551168">"Емират"</string>
+ <string name="permlab_readHistoryBookmarks" msgid="9102293913842539697">"читање веб обележивача и историје"</string>
+ <string name="permdesc_readHistoryBookmarks" msgid="2323799501008967852">"Дозвољава апликацији да чита историју свих URL адреса које су посећене помоћу Прегледача, као и све обележиваче у Прегледачу. Напомена: Ова дозвола се можда на примењује на прегледаче треће стране и друге апликације са могућношћу веб прегледања."</string>
+ <string name="permlab_writeHistoryBookmarks" msgid="6090259925187986937">"писање веб обележивача и историје"</string>
+ <string name="permdesc_writeHistoryBookmarks" product="tablet" msgid="573341025292489065">"Дозвољава апликацији да мења историју Прегледача или обележиваче ускладиштене на таблету. Ово може да омогући апликацији да брише или мења податке Прегледача. Напомена: Ова дозвола се можда на примењује на прегледаче треће стране и друге апликације са могућношћу веб прегледања."</string>
+ <string name="permdesc_writeHistoryBookmarks" product="tv" msgid="88642768580408561">"Дозвољава апликацији да мења историју прегледача или обележиваче који се чувају на Android TV уређају. Ово може да омогући апликацији да брише или мења податке прегледача. Напомена: ова дозвола се можда не примењује на прегледаче треће стране ни друге апликације са могућностима веб-прегледања."</string>
+ <string name="permdesc_writeHistoryBookmarks" product="default" msgid="2245203087160913652">"Дозвољава апликацији да мења историју Прегледача или обележиваче ускладиштене на телефону. Ово може да омогући апликацији да брише или мења податке Прегледача. Напомена: Ова дозвола се можда на примењује на прегледаче треће стране и друге апликације са могућношћу веб прегледања."</string>
+ <string name="permlab_setAlarm" msgid="1158001610254173567">"подешавање аларма"</string>
+ <string name="permdesc_setAlarm" msgid="2185033720060109640">"Дозвољава апликацији да подеси аларм у инсталираној апликацији будилника. Неке апликације будилника можда не примењују ову функцију."</string>
+ <string name="permlab_addVoicemail" msgid="4770245808840814471">"додавање говорне поште"</string>
+ <string name="permdesc_addVoicemail" msgid="5470312139820074324">"Дозвољава апликацији да додаје поруке у пријемно сандуче говорне поште."</string>
+ <string name="permlab_writeGeolocationPermissions" msgid="8605631647492879449">"измена дозвола за географске локације Прегледача"</string>
+ <string name="permdesc_writeGeolocationPermissions" msgid="5817346421222227772">"Дозвољава апликацији да измени дозволе Прегледача за утврђивање географске локације. Злонамерне апликације то могу да злоупотребе и искористе за слање информација о локацији насумичним веб-сајтовима."</string>
+ <string name="save_password_message" msgid="2146409467245462965">"Желите ли да прегледач запамти ову лозинку?"</string>
+ <string name="save_password_notnow" msgid="2878327088951240061">"Не сада"</string>
+ <string name="save_password_remember" msgid="6490888932657708341">"Запамти"</string>
+ <string name="save_password_never" msgid="6776808375903410659">"Никад"</string>
+ <string name="open_permission_deny" msgid="5136793905306987251">"Немате дозволу да отворите ову страницу."</string>
+ <string name="text_copied" msgid="2531420577879738860">"Текст је копиран у привремену меморију."</string>
+ <string name="pasted_from_app" msgid="5627698450808256545">"Апликација<xliff:g id="PASTING_APP_NAME">%1$s</xliff:g> је налепила податке из апликације <xliff:g id="SOURCE_APP_NAME">%2$s</xliff:g>"</string>
+ <string name="pasted_from_clipboard" msgid="7355790625710831847">"<xliff:g id="PASTING_APP_NAME">%1$s</xliff:g> је прелепио/ла из привремене меморије"</string>
+ <string name="pasted_text" msgid="4298871641549173733">"Апликација<xliff:g id="PASTING_APP_NAME">%1$s</xliff:g> је налепила текст који сте копирали"</string>
+ <string name="pasted_image" msgid="4729097394781491022">"Апликација<xliff:g id="PASTING_APP_NAME">%1$s</xliff:g> је налепила слику коју сте копирали"</string>
+ <string name="pasted_content" msgid="646276353060777131">"Апликација<xliff:g id="PASTING_APP_NAME">%1$s</xliff:g> је налепила садржај који сте копирали"</string>
+ <string name="more_item_label" msgid="7419249600215749115">"Још"</string>
+ <string name="prepend_shortcut_label" msgid="1743716737502867951">"Мени+"</string>
<string name="menu_meta_shortcut_label" msgid="1623390163674762478">"Meta+"</string>
<string name="menu_ctrl_shortcut_label" msgid="131911133027196485">"Ctrl+"</string>
<string name="menu_alt_shortcut_label" msgid="343761069945250991">"Alt+"</string>
<string name="menu_shift_shortcut_label" msgid="5443936876111232346">"Shift+"</string>
<string name="menu_sym_shortcut_label" msgid="4037566049061218776">"Sym+"</string>
<string name="menu_function_shortcut_label" msgid="2367112760987662566">"Function+"</string>
- <string name="menu_space_shortcut_label" msgid="5949311515646872071">"razmak"</string>
+ <string name="menu_space_shortcut_label" msgid="5949311515646872071">"размак"</string>
<string name="menu_enter_shortcut_label" msgid="6709499510082897320">"enter"</string>
- <string name="menu_delete_shortcut_label" msgid="4365787714477739080">"izbriši"</string>
- <string name="search_go" msgid="2141477624421347086">"Pretraži"</string>
- <string name="search_hint" msgid="455364685740251925">"Pretražite…"</string>
- <string name="searchview_description_search" msgid="1045552007537359343">"Pretraži"</string>
- <string name="searchview_description_query" msgid="7430242366971716338">"Upit za pretragu"</string>
- <string name="searchview_description_clear" msgid="1989371719192982900">"Obriši upit"</string>
- <string name="searchview_description_submit" msgid="6771060386117334686">"Pošalji upit"</string>
- <string name="searchview_description_voice" msgid="42360159504884679">"Glasovna pretraga"</string>
- <string name="enable_explore_by_touch_warning_title" msgid="5095399706284943314">"Omogućiti Istraživanje dodirom?"</string>
- <string name="enable_explore_by_touch_warning_message" product="tablet" msgid="1037295476738940824">"<xliff:g id="ACCESSIBILITY_SERVICE_NAME">%1$s</xliff:g> želi da omogući Istraživanje dodirom. Kada je Istraživanje dodirom uključeno, možete da čujete ili vidite opise stavke na koju ste stavili prst ili da komunicirate sa tabletom pomoću pokreta."</string>
- <string name="enable_explore_by_touch_warning_message" product="default" msgid="4312979647356179250">"<xliff:g id="ACCESSIBILITY_SERVICE_NAME">%1$s</xliff:g> želi da omogući Istraživanje dodirom. Kada je Istraživanje dodirom uključeno, možete da čujete ili vidite opise stavke na koju ste stavili prst ili da komunicirate sa telefonom pomoću pokreta."</string>
- <string name="oneMonthDurationPast" msgid="4538030857114635777">"Pre mesec dana"</string>
- <string name="beforeOneMonthDurationPast" msgid="8315149541372065392">"Pre mesec dana"</string>
- <string name="last_num_days" msgid="2393660431490280537">"{count,plural, =1{Poslednji # dan}one{Poslednji # dan}few{Poslednja # dana}other{Poslednjih # dana}}"</string>
- <string name="last_month" msgid="1528906781083518683">"Prošlog meseca"</string>
- <string name="older" msgid="1645159827884647400">"Starije"</string>
+ <string name="menu_delete_shortcut_label" msgid="4365787714477739080">"избриши"</string>
+ <string name="search_go" msgid="2141477624421347086">"Претражи"</string>
+ <string name="search_hint" msgid="455364685740251925">"Претражите…"</string>
+ <string name="searchview_description_search" msgid="1045552007537359343">"Претражи"</string>
+ <string name="searchview_description_query" msgid="7430242366971716338">"Упит за претрагу"</string>
+ <string name="searchview_description_clear" msgid="1989371719192982900">"Обриши упит"</string>
+ <string name="searchview_description_submit" msgid="6771060386117334686">"Пошаљи упит"</string>
+ <string name="searchview_description_voice" msgid="42360159504884679">"Гласовна претрага"</string>
+ <string name="enable_explore_by_touch_warning_title" msgid="5095399706284943314">"Oмогућити Истраживање додиром?"</string>
+ <string name="enable_explore_by_touch_warning_message" product="tablet" msgid="1037295476738940824">"<xliff:g id="ACCESSIBILITY_SERVICE_NAME">%1$s</xliff:g> жели да омогући Истраживање додиром. Када је Истраживање додиром укључено, можете да чујете или видите описе ставке на коју сте ставили прст или да комуницирате са таблетом помоћу покрета."</string>
+ <string name="enable_explore_by_touch_warning_message" product="default" msgid="4312979647356179250">"<xliff:g id="ACCESSIBILITY_SERVICE_NAME">%1$s</xliff:g> жели да омогући Истраживање додиром. Када је Истраживање додиром укључено, можете да чујете или видите описе ставке на коју сте ставили прст или да комуницирате са телефоном помоћу покрета."</string>
+ <string name="oneMonthDurationPast" msgid="4538030857114635777">"Пре месец дана"</string>
+ <string name="beforeOneMonthDurationPast" msgid="8315149541372065392">"Пре месец дана"</string>
+ <string name="last_num_days" msgid="2393660431490280537">"{count,plural, =1{Последњи # дан}one{Последњи # дан}few{Последња # дана}other{Последњих # дана}}"</string>
+ <string name="last_month" msgid="1528906781083518683">"Прошлог месеца"</string>
+ <string name="older" msgid="1645159827884647400">"Старије"</string>
<string name="preposition_for_date" msgid="2780767868832729599">"<xliff:g id="DATE">%s</xliff:g>"</string>
- <string name="preposition_for_time" msgid="4336835286453822053">"u <xliff:g id="TIME">%s</xliff:g>"</string>
- <string name="preposition_for_year" msgid="3149809685340130039">"u <xliff:g id="YEAR">%s</xliff:g>."</string>
- <string name="day" msgid="8394717255950176156">"dan"</string>
- <string name="days" msgid="4570879797423034973">"dana"</string>
- <string name="hour" msgid="7796325297097314653">"sat"</string>
- <string name="hours" msgid="8517014849629200683">"sata"</string>
- <string name="minute" msgid="8369209540986467610">"min"</string>
- <string name="minutes" msgid="3456532942641808971">"min"</string>
- <string name="second" msgid="9210875257112211713">"sek"</string>
- <string name="seconds" msgid="2175052687727971048">"sek"</string>
- <string name="week" msgid="907127093960923779">"nedelja"</string>
- <string name="weeks" msgid="3516247214269821391">"nedelje/a"</string>
- <string name="year" msgid="5182610307741238982">"godina"</string>
- <string name="years" msgid="5797714729103773425">"godine(a)"</string>
- <string name="now_string_shortest" msgid="3684914126941650330">"sada"</string>
- <string name="duration_minutes_shortest" msgid="5744379079540806690">"<xliff:g id="COUNT">%d</xliff:g> min"</string>
- <string name="duration_hours_shortest" msgid="1477752094141971675">"<xliff:g id="COUNT">%d</xliff:g> s"</string>
- <string name="duration_days_shortest" msgid="4083124701676227233">"<xliff:g id="COUNT">%d</xliff:g> d"</string>
- <string name="duration_years_shortest" msgid="483982719231145618">"<xliff:g id="COUNT">%d</xliff:g> god"</string>
- <string name="duration_minutes_shortest_future" msgid="5260857299282734759">"za <xliff:g id="COUNT">%d</xliff:g> min"</string>
- <string name="duration_hours_shortest_future" msgid="2979276794547981674">"za <xliff:g id="COUNT">%d</xliff:g> s"</string>
- <string name="duration_days_shortest_future" msgid="3392722163935571543">"za <xliff:g id="COUNT">%d</xliff:g> d"</string>
- <string name="duration_years_shortest_future" msgid="5537464088352970388">"za <xliff:g id="COUNT">%d</xliff:g> god"</string>
- <string name="duration_minutes_relative" msgid="8620337701051015593">"{count,plural, =1{Pre # minut}one{Pre # minut}few{Pre # minuta}other{Pre # minuta}}"</string>
- <string name="duration_hours_relative" msgid="4836449961693180253">"{count,plural, =1{Pre # sat}one{Pre # sat}few{Pre # sata}other{Pre # sati}}"</string>
- <string name="duration_days_relative" msgid="621965767567258302">"{count,plural, =1{Pre # dan}one{Pre # dan}few{Pre # dana}other{Pre # dana}}"</string>
- <string name="duration_years_relative" msgid="8731202348869424370">"{count,plural, =1{Pre # godinu}one{Pre # godinu}few{Pre # godine}other{Pre # godina}}"</string>
- <string name="duration_minutes_relative_future" msgid="5259574171747708115">"{count,plural, =1{# minut}one{# minut}few{# minuta}other{# minuta}}"</string>
- <string name="duration_hours_relative_future" msgid="6670440478481140565">"{count,plural, =1{# sat}one{# sat}few{# sata}other{# sati}}"</string>
- <string name="duration_days_relative_future" msgid="8870658635774250746">"{count,plural, =1{# dan}one{# dan}few{# dana}other{# dana}}"</string>
- <string name="duration_years_relative_future" msgid="8855853883925918380">"{count,plural, =1{# godina}one{# godina}few{# godine}other{# godina}}"</string>
- <string name="VideoView_error_title" msgid="5750686717225068016">"Problem sa video snimkom"</string>
- <string name="VideoView_error_text_invalid_progressive_playback" msgid="3782449246085134720">"Ovaj video ne može da se strimuje na ovom uređaju."</string>
- <string name="VideoView_error_text_unknown" msgid="7658683339707607138">"Ne možete da pustite ovaj video."</string>
- <string name="VideoView_error_button" msgid="5138809446603764272">"Potvrdi"</string>
+ <string name="preposition_for_time" msgid="4336835286453822053">"у <xliff:g id="TIME">%s</xliff:g>"</string>
+ <string name="preposition_for_year" msgid="3149809685340130039">"у <xliff:g id="YEAR">%s</xliff:g>."</string>
+ <string name="day" msgid="8394717255950176156">"дан"</string>
+ <string name="days" msgid="4570879797423034973">"дана"</string>
+ <string name="hour" msgid="7796325297097314653">"сат"</string>
+ <string name="hours" msgid="8517014849629200683">"сата"</string>
+ <string name="minute" msgid="8369209540986467610">"мин"</string>
+ <string name="minutes" msgid="3456532942641808971">"мин"</string>
+ <string name="second" msgid="9210875257112211713">"сек"</string>
+ <string name="seconds" msgid="2175052687727971048">"сек"</string>
+ <string name="week" msgid="907127093960923779">"недеља"</string>
+ <string name="weeks" msgid="3516247214269821391">"недеље/а"</string>
+ <string name="year" msgid="5182610307741238982">"година"</string>
+ <string name="years" msgid="5797714729103773425">"годинe(а)"</string>
+ <string name="now_string_shortest" msgid="3684914126941650330">"сада"</string>
+ <string name="duration_minutes_shortest" msgid="5744379079540806690">"<xliff:g id="COUNT">%d</xliff:g> мин"</string>
+ <string name="duration_hours_shortest" msgid="1477752094141971675">"<xliff:g id="COUNT">%d</xliff:g> с"</string>
+ <string name="duration_days_shortest" msgid="4083124701676227233">"<xliff:g id="COUNT">%d</xliff:g> д"</string>
+ <string name="duration_years_shortest" msgid="483982719231145618">"<xliff:g id="COUNT">%d</xliff:g> год"</string>
+ <string name="duration_minutes_shortest_future" msgid="5260857299282734759">"за <xliff:g id="COUNT">%d</xliff:g> мин"</string>
+ <string name="duration_hours_shortest_future" msgid="2979276794547981674">"за <xliff:g id="COUNT">%d</xliff:g> с"</string>
+ <string name="duration_days_shortest_future" msgid="3392722163935571543">"за <xliff:g id="COUNT">%d</xliff:g> д"</string>
+ <string name="duration_years_shortest_future" msgid="5537464088352970388">"за <xliff:g id="COUNT">%d</xliff:g> год"</string>
+ <string name="duration_minutes_relative" msgid="8620337701051015593">"{count,plural, =1{Пре # минут}one{Пре # минут}few{Пре # минута}other{Пре # минута}}"</string>
+ <string name="duration_hours_relative" msgid="4836449961693180253">"{count,plural, =1{Пре # сат}one{Пре # сат}few{Пре # сата}other{Пре # сати}}"</string>
+ <string name="duration_days_relative" msgid="621965767567258302">"{count,plural, =1{Пре # дан}one{Пре # дан}few{Пре # дана}other{Пре # дана}}"</string>
+ <string name="duration_years_relative" msgid="8731202348869424370">"{count,plural, =1{Пре # годину}one{Пре # годину}few{Пре # године}other{Пре # година}}"</string>
+ <string name="duration_minutes_relative_future" msgid="5259574171747708115">"{count,plural, =1{# минут}one{# минут}few{# минута}other{# минута}}"</string>
+ <string name="duration_hours_relative_future" msgid="6670440478481140565">"{count,plural, =1{# сат}one{# сат}few{# сата}other{# сати}}"</string>
+ <string name="duration_days_relative_future" msgid="8870658635774250746">"{count,plural, =1{# дан}one{# дан}few{# дана}other{# дана}}"</string>
+ <string name="duration_years_relative_future" msgid="8855853883925918380">"{count,plural, =1{# година}one{# година}few{# године}other{# година}}"</string>
+ <string name="VideoView_error_title" msgid="5750686717225068016">"Проблем са видео снимком"</string>
+ <string name="VideoView_error_text_invalid_progressive_playback" msgid="3782449246085134720">"Овај видео не може да се стримује на овом уређају."</string>
+ <string name="VideoView_error_text_unknown" msgid="7658683339707607138">"Не можете да пустите овај видео."</string>
+ <string name="VideoView_error_button" msgid="5138809446603764272">"Потврди"</string>
<string name="relative_time" msgid="8572030016028033243">"<xliff:g id="DATE">%1$s</xliff:g>, <xliff:g id="TIME">%2$s</xliff:g>"</string>
- <string name="noon" msgid="8365974533050605886">"podne"</string>
- <string name="Noon" msgid="6902418443846838189">"Podne"</string>
- <string name="midnight" msgid="3646671134282785114">"ponoć"</string>
- <string name="Midnight" msgid="8176019203622191377">"Ponoć"</string>
+ <string name="noon" msgid="8365974533050605886">"подне"</string>
+ <string name="Noon" msgid="6902418443846838189">"Подне"</string>
+ <string name="midnight" msgid="3646671134282785114">"поноћ"</string>
+ <string name="Midnight" msgid="8176019203622191377">"Поноћ"</string>
<string name="elapsed_time_short_format_mm_ss" msgid="8689459651807876423">"<xliff:g id="MINUTES">%1$02d</xliff:g>:<xliff:g id="SECONDS">%2$02d</xliff:g>"</string>
<string name="elapsed_time_short_format_h_mm_ss" msgid="2302144714803345056">"<xliff:g id="HOURS">%1$d</xliff:g>:<xliff:g id="MINUTES">%2$02d</xliff:g>:<xliff:g id="SECONDS">%3$02d</xliff:g>"</string>
- <string name="selectAll" msgid="1532369154488982046">"Izaberi sve"</string>
- <string name="cut" msgid="2561199725874745819">"Iseci"</string>
- <string name="copy" msgid="5472512047143665218">"Kopiraj"</string>
- <string name="failed_to_copy_to_clipboard" msgid="725919885138539875">"Kopiranje u privremenu memoriju nije uspelo"</string>
- <string name="paste" msgid="461843306215520225">"Nalepi"</string>
- <string name="paste_as_plain_text" msgid="7664800665823182587">"Nalepi kao običan tekst"</string>
- <string name="replace" msgid="7842675434546657444">"Zameni..."</string>
- <string name="delete" msgid="1514113991712129054">"Izbriši"</string>
- <string name="copyUrl" msgid="6229645005987260230">"Kopiraj URL adresu"</string>
- <string name="selectTextMode" msgid="3225108910999318778">"Izaberi tekst"</string>
- <string name="undo" msgid="3175318090002654673">"Opozovi"</string>
- <string name="redo" msgid="7231448494008532233">"Ponovi"</string>
- <string name="autofill" msgid="511224882647795296">"Automatsko popunjavanje"</string>
- <string name="textSelectionCABTitle" msgid="5151441579532476940">"Izbor teksta"</string>
- <string name="addToDictionary" msgid="8041821113480950096">"Dodaj u rečnik"</string>
- <string name="deleteText" msgid="4200807474529938112">"Izbriši"</string>
- <string name="inputMethod" msgid="1784759500516314751">"Metod unosa"</string>
- <string name="editTextMenuTitle" msgid="857666911134482176">"Radnje u vezi sa tekstom"</string>
- <string name="input_method_nav_back_button_desc" msgid="3655838793765691787">"Nazad"</string>
- <string name="input_method_ime_switch_button_desc" msgid="2736542240252198501">"Promenite metod unosa"</string>
- <string name="low_internal_storage_view_title" msgid="9024241779284783414">"Memorijski prostor je na izmaku"</string>
- <string name="low_internal_storage_view_text" msgid="8172166728369697835">"Neke sistemske funkcije možda ne funkcionišu"</string>
- <string name="low_internal_storage_view_text_no_boot" msgid="7368968163411251788">"Nema dovoljno memorijskog prostora za sistem. Uverite se da imate 250 MB slobodnog prostora i ponovo pokrenite."</string>
- <string name="app_running_notification_title" msgid="8985999749231486569">"Aplikacija <xliff:g id="APP_NAME">%1$s</xliff:g> je pokrenuta"</string>
- <string name="app_running_notification_text" msgid="5120815883400228566">"Dodirnite za više informacija ili zaustavljanje aplikacije."</string>
- <string name="ok" msgid="2646370155170753815">"Potvrdi"</string>
- <string name="cancel" msgid="6908697720451760115">"Otkaži"</string>
- <string name="yes" msgid="9069828999585032361">"Potvrdi"</string>
- <string name="no" msgid="5122037903299899715">"Otkaži"</string>
- <string name="dialog_alert_title" msgid="651856561974090712">"Pažnja"</string>
- <string name="loading" msgid="3138021523725055037">"Učitava se…"</string>
- <string name="capital_on" msgid="2770685323900821829">"DA"</string>
- <string name="capital_off" msgid="7443704171014626777">"NE"</string>
- <string name="checked" msgid="9179896827054513119">"označeno je"</string>
- <string name="not_checked" msgid="7972320087569023342">"nije označeno"</string>
- <string name="selected" msgid="6614607926197755875">"izabrano"</string>
- <string name="not_selected" msgid="410652016565864475">"nije izabrano"</string>
- <string name="rating_label" msgid="1837085249662154601">"{rating,plural, =1{Jedna zvezdica od {max}}one{# zvezdica od {max}}few{# zvezdice od {max}}other{# zvezdica od {max}}}"</string>
- <string name="in_progress" msgid="2149208189184319441">"u toku"</string>
- <string name="whichApplication" msgid="5432266899591255759">"Dovrši radnju preko"</string>
- <string name="whichApplicationNamed" msgid="6969946041713975681">"Završite radnju pomoću aplikacije %1$s"</string>
- <string name="whichApplicationLabel" msgid="7852182961472531728">"Završi radnju"</string>
- <string name="whichViewApplication" msgid="5733194231473132945">"Otvorite pomoću"</string>
- <string name="whichViewApplicationNamed" msgid="415164730629690105">"Otvorite pomoću aplikacije %1$s"</string>
- <string name="whichViewApplicationLabel" msgid="7367556735684742409">"Otvori"</string>
- <string name="whichOpenHostLinksWith" msgid="7645631470199397485">"Otvarajte <xliff:g id="HOST">%1$s</xliff:g> linkove pomoću"</string>
- <string name="whichOpenLinksWith" msgid="1120936181362907258">"Otvaraj linkove pomoću"</string>
- <string name="whichOpenLinksWithApp" msgid="6917864367861910086">"Otvarajte linkove pomoću aplikacije <xliff:g id="APPLICATION">%1$s</xliff:g>"</string>
- <string name="whichOpenHostLinksWithApp" msgid="2401668560768463004">"Otvarajte <xliff:g id="HOST">%1$s</xliff:g> linkove pomoću aplikacije <xliff:g id="APPLICATION">%2$s</xliff:g>"</string>
- <string name="whichGiveAccessToApplicationLabel" msgid="7805857277166106236">"Dozvoli pristup"</string>
- <string name="whichEditApplication" msgid="6191568491456092812">"Izmenite pomoću"</string>
- <string name="whichEditApplicationNamed" msgid="8096494987978521514">"Izmenite pomoću aplikacije %1$s"</string>
- <string name="whichEditApplicationLabel" msgid="1463288652070140285">"Izmeni"</string>
- <string name="whichSendApplication" msgid="4143847974460792029">"Delite"</string>
- <string name="whichSendApplicationNamed" msgid="4470386782693183461">"Delite pomoću aplikacije %1$s"</string>
- <string name="whichSendApplicationLabel" msgid="7467813004769188515">"Deli"</string>
- <string name="whichSendToApplication" msgid="77101541959464018">"Pošaljite pomoću:"</string>
- <string name="whichSendToApplicationNamed" msgid="3385686512014670003">"Pošaljite pomoću: %1$s"</string>
- <string name="whichSendToApplicationLabel" msgid="3543240188816513303">"Pošalji"</string>
- <string name="whichHomeApplication" msgid="8276350727038396616">"Izaberite aplikaciju za početnu stranicu"</string>
- <string name="whichHomeApplicationNamed" msgid="5855990024847433794">"Koristite %1$s za početnu"</string>
- <string name="whichHomeApplicationLabel" msgid="8907334282202933959">"Snimite sliku"</string>
- <string name="whichImageCaptureApplication" msgid="2737413019463215284">"Snimite sliku pomoću aplikacije"</string>
- <string name="whichImageCaptureApplicationNamed" msgid="8820702441847612202">"Snimite sliku pomoću aplikacije %1$s"</string>
- <string name="whichImageCaptureApplicationLabel" msgid="6505433734824988277">"Snimite sliku"</string>
- <string name="alwaysUse" msgid="3153558199076112903">"Podrazumevano koristi za ovu radnju."</string>
- <string name="use_a_different_app" msgid="4987790276170972776">"Koristite drugu aplikaciju"</string>
- <string name="clearDefaultHintMsg" msgid="1325866337702524936">"Obrišite podrazumevano podešavanje u meniju Podešavanja sistema > Aplikacije > Preuzeto."</string>
- <string name="chooseActivity" msgid="8563390197659779956">"Izaberite radnju"</string>
- <string name="chooseUsbActivity" msgid="2096269989990986612">"Izbor aplikacije za USB uređaj"</string>
- <string name="noApplications" msgid="1186909265235544019">"Nijedna aplikacija ne može da obavlja ovu radnju."</string>
- <string name="aerr_application" msgid="4090916809370389109">"Aplikacija <xliff:g id="APPLICATION">%1$s</xliff:g> je zaustavljena"</string>
- <string name="aerr_process" msgid="4268018696970966407">"Proces <xliff:g id="PROCESS">%1$s</xliff:g> je zaustavljen"</string>
- <string name="aerr_application_repeated" msgid="7804378743218496566">"<xliff:g id="APPLICATION">%1$s</xliff:g> se stalno zaustavlja"</string>
- <string name="aerr_process_repeated" msgid="1153152413537954974">"Proces <xliff:g id="PROCESS">%1$s</xliff:g> se stalno zaustavlja"</string>
- <string name="aerr_restart" msgid="2789618625210505419">"Ponovo otvori aplikaciju"</string>
- <string name="aerr_report" msgid="3095644466849299308">"Pošaljite povratne informacije"</string>
- <string name="aerr_close" msgid="3398336821267021852">"Zatvori"</string>
- <string name="aerr_mute" msgid="2304972923480211376">"Ignoriši dok se uređaj ne pokrene ponovo"</string>
- <string name="aerr_wait" msgid="3198677780474548217">"Čekaj"</string>
- <string name="aerr_close_app" msgid="8318883106083050970">"Zatvori aplikaciju"</string>
+ <string name="selectAll" msgid="1532369154488982046">"Изабери све"</string>
+ <string name="cut" msgid="2561199725874745819">"Исеци"</string>
+ <string name="copy" msgid="5472512047143665218">"Копирај"</string>
+ <string name="failed_to_copy_to_clipboard" msgid="725919885138539875">"Копирање у привремену меморију није успело"</string>
+ <string name="paste" msgid="461843306215520225">"Налепи"</string>
+ <string name="paste_as_plain_text" msgid="7664800665823182587">"Налепи као обичан текст"</string>
+ <string name="replace" msgid="7842675434546657444">"Замени..."</string>
+ <string name="delete" msgid="1514113991712129054">"Избриши"</string>
+ <string name="copyUrl" msgid="6229645005987260230">"Копирај URL адресу"</string>
+ <string name="selectTextMode" msgid="3225108910999318778">"Изабери текст"</string>
+ <string name="undo" msgid="3175318090002654673">"Опозови"</string>
+ <string name="redo" msgid="7231448494008532233">"Понови"</string>
+ <string name="autofill" msgid="511224882647795296">"Аутоматско попуњавање"</string>
+ <string name="textSelectionCABTitle" msgid="5151441579532476940">"Избор текста"</string>
+ <string name="addToDictionary" msgid="8041821113480950096">"Додај у речник"</string>
+ <string name="deleteText" msgid="4200807474529938112">"Избриши"</string>
+ <string name="inputMethod" msgid="1784759500516314751">"Метод уноса"</string>
+ <string name="editTextMenuTitle" msgid="857666911134482176">"Радње у вези са текстом"</string>
+ <string name="input_method_nav_back_button_desc" msgid="3655838793765691787">"Назад"</string>
+ <string name="input_method_ime_switch_button_desc" msgid="2736542240252198501">"Промените метод уноса"</string>
+ <string name="low_internal_storage_view_title" msgid="9024241779284783414">"Меморијски простор је на измаку"</string>
+ <string name="low_internal_storage_view_text" msgid="8172166728369697835">"Неке системске функције можда не функционишу"</string>
+ <string name="low_internal_storage_view_text_no_boot" msgid="7368968163411251788">"Нема довољно меморијског простора за систем. Уверите се да имате 250 MB слободног простора и поново покрените."</string>
+ <string name="app_running_notification_title" msgid="8985999749231486569">"Апликација <xliff:g id="APP_NAME">%1$s</xliff:g> је покренута"</string>
+ <string name="app_running_notification_text" msgid="5120815883400228566">"Додирните за више информација или заустављање апликације."</string>
+ <string name="ok" msgid="2646370155170753815">"Потврди"</string>
+ <string name="cancel" msgid="6908697720451760115">"Откажи"</string>
+ <string name="yes" msgid="9069828999585032361">"Потврди"</string>
+ <string name="no" msgid="5122037903299899715">"Откажи"</string>
+ <string name="dialog_alert_title" msgid="651856561974090712">"Пажња"</string>
+ <string name="loading" msgid="3138021523725055037">"Учитава се…"</string>
+ <string name="capital_on" msgid="2770685323900821829">"ДА"</string>
+ <string name="capital_off" msgid="7443704171014626777">"НЕ"</string>
+ <string name="checked" msgid="9179896827054513119">"означено је"</string>
+ <string name="not_checked" msgid="7972320087569023342">"није означено"</string>
+ <string name="selected" msgid="6614607926197755875">"изабрано"</string>
+ <string name="not_selected" msgid="410652016565864475">"није изабрано"</string>
+ <string name="rating_label" msgid="1837085249662154601">"{rating,plural, =1{Једна звездица од {max}}one{# звездица од {max}}few{# звездице од {max}}other{# звездица од {max}}}"</string>
+ <string name="in_progress" msgid="2149208189184319441">"у току"</string>
+ <string name="whichApplication" msgid="5432266899591255759">"Доврши радњу преко"</string>
+ <string name="whichApplicationNamed" msgid="6969946041713975681">"Завршите радњу помоћу апликације %1$s"</string>
+ <string name="whichApplicationLabel" msgid="7852182961472531728">"Заврши радњу"</string>
+ <string name="whichViewApplication" msgid="5733194231473132945">"Отворите помоћу"</string>
+ <string name="whichViewApplicationNamed" msgid="415164730629690105">"Отворите помоћу апликације %1$s"</string>
+ <string name="whichViewApplicationLabel" msgid="7367556735684742409">"Отвори"</string>
+ <string name="whichOpenHostLinksWith" msgid="7645631470199397485">"Отварајте <xliff:g id="HOST">%1$s</xliff:g> линкове помоћу"</string>
+ <string name="whichOpenLinksWith" msgid="1120936181362907258">"Отварај линкове помоћу"</string>
+ <string name="whichOpenLinksWithApp" msgid="6917864367861910086">"Отварајте линкове помоћу апликације <xliff:g id="APPLICATION">%1$s</xliff:g>"</string>
+ <string name="whichOpenHostLinksWithApp" msgid="2401668560768463004">"Отварајте <xliff:g id="HOST">%1$s</xliff:g> линкове помоћу апликације <xliff:g id="APPLICATION">%2$s</xliff:g>"</string>
+ <string name="whichGiveAccessToApplicationLabel" msgid="7805857277166106236">"Дозволи приступ"</string>
+ <string name="whichEditApplication" msgid="6191568491456092812">"Измените помоћу"</string>
+ <string name="whichEditApplicationNamed" msgid="8096494987978521514">"Измените помоћу апликације %1$s"</string>
+ <string name="whichEditApplicationLabel" msgid="1463288652070140285">"Измени"</string>
+ <string name="whichSendApplication" msgid="4143847974460792029">"Делите"</string>
+ <string name="whichSendApplicationNamed" msgid="4470386782693183461">"Делите помоћу апликације %1$s"</string>
+ <string name="whichSendApplicationLabel" msgid="7467813004769188515">"Дели"</string>
+ <string name="whichSendToApplication" msgid="77101541959464018">"Пошаљите помоћу:"</string>
+ <string name="whichSendToApplicationNamed" msgid="3385686512014670003">"Пошаљите помоћу: %1$s"</string>
+ <string name="whichSendToApplicationLabel" msgid="3543240188816513303">"Пошаљи"</string>
+ <string name="whichHomeApplication" msgid="8276350727038396616">"Изаберите апликацију за почетну страницу"</string>
+ <string name="whichHomeApplicationNamed" msgid="5855990024847433794">"Користите %1$s за почетну"</string>
+ <string name="whichHomeApplicationLabel" msgid="8907334282202933959">"Снимите слику"</string>
+ <string name="whichImageCaptureApplication" msgid="2737413019463215284">"Снимите слику помоћу апликације"</string>
+ <string name="whichImageCaptureApplicationNamed" msgid="8820702441847612202">"Снимите слику помоћу апликације %1$s"</string>
+ <string name="whichImageCaptureApplicationLabel" msgid="6505433734824988277">"Снимите слику"</string>
+ <string name="alwaysUse" msgid="3153558199076112903">"Подразумевано користи за ову радњу."</string>
+ <string name="use_a_different_app" msgid="4987790276170972776">"Користите другу апликацију"</string>
+ <string name="clearDefaultHintMsg" msgid="1325866337702524936">"Обришите подразумевано подешавање у менију Подешавања система > Апликације > Преузето."</string>
+ <string name="chooseActivity" msgid="8563390197659779956">"Изаберите радњу"</string>
+ <string name="chooseUsbActivity" msgid="2096269989990986612">"Избор апликације за USB уређај"</string>
+ <string name="noApplications" msgid="1186909265235544019">"Ниједна апликација не може да обавља ову радњу."</string>
+ <string name="aerr_application" msgid="4090916809370389109">"Апликација <xliff:g id="APPLICATION">%1$s</xliff:g> је заустављена"</string>
+ <string name="aerr_process" msgid="4268018696970966407">"Процес <xliff:g id="PROCESS">%1$s</xliff:g> је заустављен"</string>
+ <string name="aerr_application_repeated" msgid="7804378743218496566">"<xliff:g id="APPLICATION">%1$s</xliff:g> се стално зауставља"</string>
+ <string name="aerr_process_repeated" msgid="1153152413537954974">"Процес <xliff:g id="PROCESS">%1$s</xliff:g> се стално зауставља"</string>
+ <string name="aerr_restart" msgid="2789618625210505419">"Поново отвори апликацију"</string>
+ <string name="aerr_report" msgid="3095644466849299308">"Пошаљите повратне информације"</string>
+ <string name="aerr_close" msgid="3398336821267021852">"Затвори"</string>
+ <string name="aerr_mute" msgid="2304972923480211376">"Игнориши док се уређај не покрене поново"</string>
+ <string name="aerr_wait" msgid="3198677780474548217">"Чекај"</string>
+ <string name="aerr_close_app" msgid="8318883106083050970">"Затвори апликацију"</string>
<string name="anr_title" msgid="7290329487067300120"></string>
- <string name="anr_activity_application" msgid="8121716632960340680">"<xliff:g id="APPLICATION">%2$s</xliff:g> ne reaguje"</string>
- <string name="anr_activity_process" msgid="3477362583767128667">"<xliff:g id="ACTIVITY">%1$s</xliff:g> ne reaguje"</string>
- <string name="anr_application_process" msgid="4978772139461676184">"<xliff:g id="APPLICATION">%1$s</xliff:g> ne reaguje"</string>
- <string name="anr_process" msgid="1664277165911816067">"Proces <xliff:g id="PROCESS">%1$s</xliff:g> ne reaguje"</string>
- <string name="force_close" msgid="9035203496368973803">"Potvrdi"</string>
- <string name="report" msgid="2149194372340349521">"Prijavi"</string>
- <string name="wait" msgid="7765985809494033348">"Sačekaj"</string>
- <string name="webpage_unresponsive" msgid="7850879412195273433">"Stranica je prestala da se odaziva.\n\n Da li želite da je zatvorite?"</string>
- <string name="launch_warning_title" msgid="6725456009564953595">"Aplikacija je preusmerena"</string>
- <string name="launch_warning_replace" msgid="3073392976283203402">"Aplikacija <xliff:g id="APP_NAME">%1$s</xliff:g> je sada pokrenuta."</string>
- <string name="launch_warning_original" msgid="3332206576800169626">"Prvobitno je pokrenuta aplikacija <xliff:g id="APP_NAME">%1$s</xliff:g>."</string>
- <string name="screen_compat_mode_scale" msgid="8627359598437527726">"Razmera"</string>
- <string name="screen_compat_mode_show" msgid="5080361367584709857">"Uvek prikazuj"</string>
- <string name="screen_compat_mode_hint" msgid="4032272159093750908">"Ponovo omogućite u meniju Sistemska podešavanja > Aplikacije > Preuzeto."</string>
- <string name="unsupported_display_size_message" msgid="7265211375269394699">"<xliff:g id="APP_NAME">%1$s</xliff:g> ne podržava trenutno podešavanje veličine prikaza i može da se ponaša neočekivano."</string>
- <string name="unsupported_display_size_show" msgid="980129850974919375">"Uvek prikazuj"</string>
- <string name="unsupported_compile_sdk_message" msgid="7326293500707890537">"Aplikacija <xliff:g id="APP_NAME">%1$s</xliff:g> je napravljena za nekompatibilnu verziju Android OS-a i može da se ponaša na neočekivan način. Možda je dostupna ažurirana verzija aplikacije."</string>
- <string name="unsupported_compile_sdk_show" msgid="1601210057960312248">"Uvek prikaži"</string>
- <string name="unsupported_compile_sdk_check_update" msgid="1103639989147664456">"Potraži ažuriranje"</string>
- <string name="smv_application" msgid="3775183542777792638">"Aplikacija <xliff:g id="APPLICATION">%1$s</xliff:g> (proces <xliff:g id="PROCESS">%2$s</xliff:g>) je prekršila samonametnute StrictMode smernice."</string>
- <string name="smv_process" msgid="1398801497130695446">"Proces <xliff:g id="PROCESS">%1$s</xliff:g> je prekršio samonametnute StrictMode smernice."</string>
- <string name="android_upgrading_title" product="default" msgid="7279077384220829683">"Telefon se ažurira…"</string>
- <string name="android_upgrading_title" product="tablet" msgid="4268417249079938805">"Tablet se ažurira…"</string>
- <string name="android_upgrading_title" product="device" msgid="6774767702998149762">"Uređaj se ažurira…"</string>
- <string name="android_start_title" product="default" msgid="4036708252778757652">"Telefon se pokreće…"</string>
- <string name="android_start_title" product="automotive" msgid="7917984412828168079">"Android se pokreće…"</string>
- <string name="android_start_title" product="tablet" msgid="4429767260263190344">"Tablet se pokreće…"</string>
- <string name="android_start_title" product="device" msgid="6967413819673299309">"Uređaj se pokreće…"</string>
- <string name="android_upgrading_fstrim" msgid="3259087575528515329">"Memorija se optimizuje."</string>
- <string name="android_upgrading_notification_title" product="default" msgid="3509927005342279257">"Ažuriranje sistema se dovršava…"</string>
- <string name="app_upgrading_toast" msgid="1016267296049455585">"<xliff:g id="APPLICATION">%1$s</xliff:g> se nadograđuje…"</string>
- <string name="android_upgrading_apk" msgid="1339564803894466737">"Optimizovanje aplikacije <xliff:g id="NUMBER_0">%1$d</xliff:g> od <xliff:g id="NUMBER_1">%2$d</xliff:g>."</string>
- <string name="android_preparing_apk" msgid="589736917792300956">"Priprema se <xliff:g id="APPNAME">%1$s</xliff:g>."</string>
- <string name="android_upgrading_starting_apps" msgid="6206161195076057075">"Pokretanje aplikacija."</string>
- <string name="android_upgrading_complete" msgid="409800058018374746">"Završavanje pokretanja."</string>
- <string name="fp_power_button_enrollment_message" msgid="5648173517663246140">"Pritisnuli ste dugme za uključivanje – time obično isključujete ekran.\n\nProbajte lagano da dodirnete dok podešavate otisak prsta."</string>
- <string name="fp_power_button_enrollment_title" msgid="6976841690455338563">"Završite podešavanje isključivanjem ekrana"</string>
- <string name="fp_power_button_enrollment_button_text" msgid="3199783266386029200">"Isključi"</string>
- <string name="fp_power_button_bp_title" msgid="5585506104526820067">"Nastavljate verifikaciju otiska prsta?"</string>
- <string name="fp_power_button_bp_message" msgid="2983163038168903393">"Pritisnuli ste dugme za uključivanje – time obično isključujete ekran.\n\nProbajte lagano da dodirnete da biste verifikovali otisak prsta."</string>
- <string name="fp_power_button_bp_positive_button" msgid="728945472408552251">"Isključi ekran"</string>
- <string name="fp_power_button_bp_negative_button" msgid="3971364246496775178">"Nastavi"</string>
- <string name="heavy_weight_notification" msgid="8382784283600329576">"Aplikacija <xliff:g id="APP">%1$s</xliff:g> je pokrenuta"</string>
- <string name="heavy_weight_notification_detail" msgid="6802247239468404078">"Dodirnite da biste se vratili u igru"</string>
- <string name="heavy_weight_switcher_title" msgid="3861984210040100886">"Odaberite igru"</string>
- <string name="heavy_weight_switcher_text" msgid="6814316627367160126">"Da bi performanse bile bolje, može da bude otvorena samo jedna od ovih igara."</string>
- <string name="old_app_action" msgid="725331621042848590">"Nazad na <xliff:g id="OLD_APP">%1$s</xliff:g>"</string>
- <string name="new_app_action" msgid="547772182913269801">"Otvori <xliff:g id="NEW_APP">%1$s</xliff:g>"</string>
- <string name="new_app_description" msgid="1958903080400806644">"<xliff:g id="OLD_APP">%1$s</xliff:g> će se zatvoriti bez čuvanja"</string>
- <string name="dump_heap_notification" msgid="5316644945404825032">"<xliff:g id="PROC">%1$s</xliff:g> premašuje ograničenje memorije"</string>
- <string name="dump_heap_ready_notification" msgid="2302452262927390268">"Snimak dinamičkog dela memorije za proces <xliff:g id="PROC">%1$s</xliff:g> je spreman"</string>
- <string name="dump_heap_notification_detail" msgid="8431586843001054050">"Snimak dinamičkog dela memorije je napravljen. Dodirnite za deljenje."</string>
- <string name="dump_heap_title" msgid="4367128917229233901">"Želite li da delite snimak dinamičkog dela memorije?"</string>
- <string name="dump_heap_text" msgid="1692649033835719336">"Proces <xliff:g id="PROC">%1$s</xliff:g> je premašio ograničenje memorije od <xliff:g id="SIZE">%2$s</xliff:g>. Snimak dinamičkog dela memorije je dostupan i možete da ga delite sa programerom. Budite oprezni: ovaj snimak dinamičkog dela memorije može da sadrži neke lične podatke kojima aplikacija može da pristupa."</string>
- <string name="dump_heap_system_text" msgid="6805155514925350849">"Proces <xliff:g id="PROC">%1$s</xliff:g> je premašio ograničenje memorije od <xliff:g id="SIZE">%2$s</xliff:g>. Snimak dinamičkog dela memorije je dostupan i možete da ga delite. Budite oprezni: ovaj snimak dinamičkog dela memorije može da sadrži osetljive lične podatke kojima proces može da pristupa, što može da obuhvata tekst koji ste unosili."</string>
- <string name="dump_heap_ready_text" msgid="5849618132123045516">"Snimak dinamičkog dela memorije za proces <xliff:g id="PROC">%1$s</xliff:g> je dostupan i možete da ga delite. Budite oprezni: ovaj snimak dinamičkog dela memorije može da sadrži osetljive lične podatke kojima proces može da pristupa, što može da obuhvata tekst koji ste unosili."</string>
- <string name="sendText" msgid="493003724401350724">"Izaberite radnju za tekst"</string>
- <string name="volume_ringtone" msgid="134784084629229029">"Jačina zvuka zvona"</string>
- <string name="volume_music" msgid="7727274216734955095">"Jačina zvuka medija"</string>
- <string name="volume_music_hint_playing_through_bluetooth" msgid="2614142915948898228">"Igranje preko Bluetooth-a"</string>
- <string name="volume_music_hint_silent_ringtone_selected" msgid="1514829655029062233">"Podešen je nečujni zvuk zvona"</string>
- <string name="volume_call" msgid="7625321655265747433">"Jačina zvuka dolaznog poziva"</string>
- <string name="volume_bluetooth_call" msgid="2930204618610115061">"Jačina zvuka dolazećeg poziva preko Bluetooth-a"</string>
- <string name="volume_alarm" msgid="4486241060751798448">"Jačina zvuka alarma"</string>
- <string name="volume_notification" msgid="6864412249031660057">"Jačina zvuka za obaveštenja"</string>
- <string name="volume_unknown" msgid="4041914008166576293">"Jačina zvuka"</string>
- <string name="volume_icon_description_bluetooth" msgid="7540388479345558400">"Jačina zvuka Bluetooth uređaja"</string>
- <string name="volume_icon_description_ringer" msgid="2187800636867423459">"Jačina melodije zvona"</string>
- <string name="volume_icon_description_incall" msgid="4491255105381227919">"Jačina zvuka poziva"</string>
- <string name="volume_icon_description_media" msgid="4997633254078171233">"Jačina zvuka medija"</string>
- <string name="volume_icon_description_notification" msgid="579091344110747279">"Jačina zvuka obaveštenja"</string>
- <string name="ringtone_default" msgid="9118299121288174597">"Podrazumevani zvuk zvona"</string>
- <string name="ringtone_default_with_actual" msgid="2709686194556159773">"Podrazumevano (<xliff:g id="ACTUAL_RINGTONE">%1$s</xliff:g>)"</string>
- <string name="ringtone_silent" msgid="397111123930141876">"Bez zvuka"</string>
- <string name="ringtone_picker_title" msgid="667342618626068253">"Zvukovi zvona"</string>
- <string name="ringtone_picker_title_alarm" msgid="7438934548339024767">"Zvuci alarma"</string>
- <string name="ringtone_picker_title_notification" msgid="6387191794719608122">"Zvuci obaveštenja"</string>
- <string name="ringtone_unknown" msgid="5059495249862816475">"Nepoznato"</string>
- <string name="wifi_available_sign_in" msgid="381054692557675237">"Prijavljivanje na WiFi mrežu"</string>
- <string name="network_available_sign_in" msgid="1520342291829283114">"Prijavite se na mrežu"</string>
+ <string name="anr_activity_application" msgid="8121716632960340680">"<xliff:g id="APPLICATION">%2$s</xliff:g> не реагује"</string>
+ <string name="anr_activity_process" msgid="3477362583767128667">"<xliff:g id="ACTIVITY">%1$s</xliff:g> не реагује"</string>
+ <string name="anr_application_process" msgid="4978772139461676184">"<xliff:g id="APPLICATION">%1$s</xliff:g> не реагује"</string>
+ <string name="anr_process" msgid="1664277165911816067">"Процес <xliff:g id="PROCESS">%1$s</xliff:g> не реагује"</string>
+ <string name="force_close" msgid="9035203496368973803">"Потврди"</string>
+ <string name="report" msgid="2149194372340349521">"Пријави"</string>
+ <string name="wait" msgid="7765985809494033348">"Сачекај"</string>
+ <string name="webpage_unresponsive" msgid="7850879412195273433">"Страница је престала да се одазива.\n\n Да ли желите да је затворите?"</string>
+ <string name="launch_warning_title" msgid="6725456009564953595">"Апликација је преусмерена"</string>
+ <string name="launch_warning_replace" msgid="3073392976283203402">"Апликација <xliff:g id="APP_NAME">%1$s</xliff:g> је сада покренута."</string>
+ <string name="launch_warning_original" msgid="3332206576800169626">"Првобитно је покренута апликација <xliff:g id="APP_NAME">%1$s</xliff:g>."</string>
+ <string name="screen_compat_mode_scale" msgid="8627359598437527726">"Размера"</string>
+ <string name="screen_compat_mode_show" msgid="5080361367584709857">"Увек приказуј"</string>
+ <string name="screen_compat_mode_hint" msgid="4032272159093750908">"Поново омогућите у менију Системска подешавања > Апликације > Преузето."</string>
+ <string name="unsupported_display_size_message" msgid="7265211375269394699">"<xliff:g id="APP_NAME">%1$s</xliff:g> не подржава тренутно подешавање величине приказа и може да се понаша неочекивано."</string>
+ <string name="unsupported_display_size_show" msgid="980129850974919375">"Увек приказуј"</string>
+ <string name="unsupported_compile_sdk_message" msgid="7326293500707890537">"Апликација <xliff:g id="APP_NAME">%1$s</xliff:g> је направљена за некомпатибилну верзију Android ОС-а и може да се понаша на неочекиван начин. Можда је доступна ажурирана верзија апликације."</string>
+ <string name="unsupported_compile_sdk_show" msgid="1601210057960312248">"Увек прикажи"</string>
+ <string name="unsupported_compile_sdk_check_update" msgid="1103639989147664456">"Потражи ажурирање"</string>
+ <string name="smv_application" msgid="3775183542777792638">"Апликација <xliff:g id="APPLICATION">%1$s</xliff:g> (процес <xliff:g id="PROCESS">%2$s</xliff:g>) је прекршила самонаметнуте StrictMode смернице."</string>
+ <string name="smv_process" msgid="1398801497130695446">"Процес <xliff:g id="PROCESS">%1$s</xliff:g> је прекршио самонаметнуте StrictMode смернице."</string>
+ <string name="android_upgrading_title" product="default" msgid="7279077384220829683">"Телефон се ажурира…"</string>
+ <string name="android_upgrading_title" product="tablet" msgid="4268417249079938805">"Таблет се ажурира…"</string>
+ <string name="android_upgrading_title" product="device" msgid="6774767702998149762">"Уређај се ажурира…"</string>
+ <string name="android_start_title" product="default" msgid="4036708252778757652">"Телефон се покреће…"</string>
+ <string name="android_start_title" product="automotive" msgid="7917984412828168079">"Android се покреће…"</string>
+ <string name="android_start_title" product="tablet" msgid="4429767260263190344">"Таблет се покреће…"</string>
+ <string name="android_start_title" product="device" msgid="6967413819673299309">"Уређај се покреће…"</string>
+ <string name="android_upgrading_fstrim" msgid="3259087575528515329">"Меморија се оптимизује."</string>
+ <string name="android_upgrading_notification_title" product="default" msgid="3509927005342279257">"Ажурирање система се довршава…"</string>
+ <string name="app_upgrading_toast" msgid="1016267296049455585">"<xliff:g id="APPLICATION">%1$s</xliff:g> се надограђује…"</string>
+ <string name="android_upgrading_apk" msgid="1339564803894466737">"Оптимизовање апликације <xliff:g id="NUMBER_0">%1$d</xliff:g> од <xliff:g id="NUMBER_1">%2$d</xliff:g>."</string>
+ <string name="android_preparing_apk" msgid="589736917792300956">"Припрема се <xliff:g id="APPNAME">%1$s</xliff:g>."</string>
+ <string name="android_upgrading_starting_apps" msgid="6206161195076057075">"Покретање апликација."</string>
+ <string name="android_upgrading_complete" msgid="409800058018374746">"Завршавање покретања."</string>
+ <string name="fp_power_button_enrollment_message" msgid="5648173517663246140">"Притиснули сте дугме за укључивање – тиме обично искључујете екран.\n\nПробајте лагано да додирнете док подешавате отисак прста."</string>
+ <string name="fp_power_button_enrollment_title" msgid="6976841690455338563">"Завршите подешавање искључивањем екрана"</string>
+ <string name="fp_power_button_enrollment_button_text" msgid="3199783266386029200">"Искључи"</string>
+ <string name="fp_power_button_bp_title" msgid="5585506104526820067">"Настављате верификацију отиска прста?"</string>
+ <string name="fp_power_button_bp_message" msgid="2983163038168903393">"Притиснули сте дугме за укључивање – тиме обично искључујете екран.\n\nПробајте лагано да додирнете да бисте верификовали отисак прста."</string>
+ <string name="fp_power_button_bp_positive_button" msgid="728945472408552251">"Искључи екран"</string>
+ <string name="fp_power_button_bp_negative_button" msgid="3971364246496775178">"Настави"</string>
+ <string name="heavy_weight_notification" msgid="8382784283600329576">"Апликација <xliff:g id="APP">%1$s</xliff:g> је покренута"</string>
+ <string name="heavy_weight_notification_detail" msgid="6802247239468404078">"Додирните да бисте се вратили у игру"</string>
+ <string name="heavy_weight_switcher_title" msgid="3861984210040100886">"Одаберите игру"</string>
+ <string name="heavy_weight_switcher_text" msgid="6814316627367160126">"Да би перформансе биле боље, може да буде отворена само једна од ових игара."</string>
+ <string name="old_app_action" msgid="725331621042848590">"Назад на <xliff:g id="OLD_APP">%1$s</xliff:g>"</string>
+ <string name="new_app_action" msgid="547772182913269801">"Отвори <xliff:g id="NEW_APP">%1$s</xliff:g>"</string>
+ <string name="new_app_description" msgid="1958903080400806644">"<xliff:g id="OLD_APP">%1$s</xliff:g> ће се затворити без чувања"</string>
+ <string name="dump_heap_notification" msgid="5316644945404825032">"<xliff:g id="PROC">%1$s</xliff:g> премашује ограничење меморије"</string>
+ <string name="dump_heap_ready_notification" msgid="2302452262927390268">"Снимак динамичког дела меморије за процес <xliff:g id="PROC">%1$s</xliff:g> је спреман"</string>
+ <string name="dump_heap_notification_detail" msgid="8431586843001054050">"Снимак динамичког дела меморије је направљен. Додирните за дељење."</string>
+ <string name="dump_heap_title" msgid="4367128917229233901">"Желите ли да делите снимак динамичког дела меморије?"</string>
+ <string name="dump_heap_text" msgid="1692649033835719336">"Процес <xliff:g id="PROC">%1$s</xliff:g> је премашио ограничење меморије од <xliff:g id="SIZE">%2$s</xliff:g>. Снимак динамичког дела меморије је доступан и можете да га делите са програмером. Будите опрезни: овај снимак динамичког дела меморије може да садржи неке личне податке којима апликација може да приступа."</string>
+ <string name="dump_heap_system_text" msgid="6805155514925350849">"Процес <xliff:g id="PROC">%1$s</xliff:g> је премашио ограничење меморије од <xliff:g id="SIZE">%2$s</xliff:g>. Снимак динамичког дела меморије је доступан и можете да га делите. Будите опрезни: овај снимак динамичког дела меморије може да садржи осетљиве личне податке којима процес може да приступа, што може да обухвата текст који сте уносили."</string>
+ <string name="dump_heap_ready_text" msgid="5849618132123045516">"Снимак динамичког дела меморије за процес <xliff:g id="PROC">%1$s</xliff:g> је доступан и можете да га делите. Будите опрезни: овај снимак динамичког дела меморије може да садржи осетљиве личне податке којима процес може да приступа, што може да обухвата текст који сте уносили."</string>
+ <string name="sendText" msgid="493003724401350724">"Изаберите радњу за текст"</string>
+ <string name="volume_ringtone" msgid="134784084629229029">"Јачина звука звона"</string>
+ <string name="volume_music" msgid="7727274216734955095">"Јачина звука медија"</string>
+ <string name="volume_music_hint_playing_through_bluetooth" msgid="2614142915948898228">"Играње преко Bluetooth-а"</string>
+ <string name="volume_music_hint_silent_ringtone_selected" msgid="1514829655029062233">"Подешен је нечујни звук звона"</string>
+ <string name="volume_call" msgid="7625321655265747433">"Јачина звука долазног позива"</string>
+ <string name="volume_bluetooth_call" msgid="2930204618610115061">"Јачина звука долазећег позива преко Bluetooth-а"</string>
+ <string name="volume_alarm" msgid="4486241060751798448">"Јачина звука аларма"</string>
+ <string name="volume_notification" msgid="6864412249031660057">"Јачина звука за обавештења"</string>
+ <string name="volume_unknown" msgid="4041914008166576293">"Јачина звука"</string>
+ <string name="volume_icon_description_bluetooth" msgid="7540388479345558400">"Јачина звука Bluetooth уређаја"</string>
+ <string name="volume_icon_description_ringer" msgid="2187800636867423459">"Јачина мелодије звона"</string>
+ <string name="volume_icon_description_incall" msgid="4491255105381227919">"Јачина звука позива"</string>
+ <string name="volume_icon_description_media" msgid="4997633254078171233">"Јачина звука медија"</string>
+ <string name="volume_icon_description_notification" msgid="579091344110747279">"Јачина звука обавештења"</string>
+ <string name="ringtone_default" msgid="9118299121288174597">"Подразумевани звук звона"</string>
+ <string name="ringtone_default_with_actual" msgid="2709686194556159773">"Подразумевано (<xliff:g id="ACTUAL_RINGTONE">%1$s</xliff:g>)"</string>
+ <string name="ringtone_silent" msgid="397111123930141876">"Без звука"</string>
+ <string name="ringtone_picker_title" msgid="667342618626068253">"Звукови звона"</string>
+ <string name="ringtone_picker_title_alarm" msgid="7438934548339024767">"Звуци аларма"</string>
+ <string name="ringtone_picker_title_notification" msgid="6387191794719608122">"Звуци обавештења"</string>
+ <string name="ringtone_unknown" msgid="5059495249862816475">"Непознато"</string>
+ <string name="wifi_available_sign_in" msgid="381054692557675237">"Пријављивање на WiFi мрежу"</string>
+ <string name="network_available_sign_in" msgid="1520342291829283114">"Пријавите се на мрежу"</string>
<!-- no translation found for network_available_sign_in_detailed (7520423801613396556) -->
<skip />
- <string name="wifi_no_internet" msgid="1386911698276448061">"<xliff:g id="NETWORK_SSID">%1$s</xliff:g> nema pristup internetu"</string>
- <string name="wifi_no_internet_detailed" msgid="634938444133558942">"Dodirnite za opcije"</string>
- <string name="mobile_no_internet" msgid="4014455157529909781">"Mobilna mreža nema pristup internetu"</string>
- <string name="other_networks_no_internet" msgid="6698711684200067033">"Mreža nema pristup internetu"</string>
- <string name="private_dns_broken_detailed" msgid="3709388271074611847">"Pristup privatnom DNS serveru nije uspeo"</string>
- <string name="network_partial_connectivity" msgid="4791024923851432291">"<xliff:g id="NETWORK_SSID">%1$s</xliff:g> ima ograničenu vezu"</string>
- <string name="network_partial_connectivity_detailed" msgid="5741329444564575840">"Dodirnite da biste se ipak povezali"</string>
- <string name="network_switch_metered" msgid="1531869544142283384">"Prešli ste na tip mreže <xliff:g id="NETWORK_TYPE">%1$s</xliff:g>"</string>
- <string name="network_switch_metered_detail" msgid="1358296010128405906">"Uređaj koristi tip mreže <xliff:g id="NEW_NETWORK">%1$s</xliff:g> kada tip mreže <xliff:g id="PREVIOUS_NETWORK">%2$s</xliff:g> nema pristup internetu. Možda će se naplaćivati troškovi."</string>
- <string name="network_switch_metered_toast" msgid="501662047275723743">"Prešli ste sa tipa mreže <xliff:g id="PREVIOUS_NETWORK">%1$s</xliff:g> na tip mreže <xliff:g id="NEW_NETWORK">%2$s</xliff:g>"</string>
+ <string name="wifi_no_internet" msgid="1386911698276448061">"<xliff:g id="NETWORK_SSID">%1$s</xliff:g> нема приступ интернету"</string>
+ <string name="wifi_no_internet_detailed" msgid="634938444133558942">"Додирните за опције"</string>
+ <string name="mobile_no_internet" msgid="4014455157529909781">"Мобилна мрежа нема приступ интернету"</string>
+ <string name="other_networks_no_internet" msgid="6698711684200067033">"Мрежа нема приступ интернету"</string>
+ <string name="private_dns_broken_detailed" msgid="3709388271074611847">"Приступ приватном DNS серверу није успео"</string>
+ <string name="network_partial_connectivity" msgid="4791024923851432291">"<xliff:g id="NETWORK_SSID">%1$s</xliff:g> има ограничену везу"</string>
+ <string name="network_partial_connectivity_detailed" msgid="5741329444564575840">"Додирните да бисте се ипак повезали"</string>
+ <string name="network_switch_metered" msgid="1531869544142283384">"Прешли сте на тип мреже <xliff:g id="NETWORK_TYPE">%1$s</xliff:g>"</string>
+ <string name="network_switch_metered_detail" msgid="1358296010128405906">"Уређај користи тип мреже <xliff:g id="NEW_NETWORK">%1$s</xliff:g> када тип мреже <xliff:g id="PREVIOUS_NETWORK">%2$s</xliff:g> нема приступ интернету. Можда ће се наплаћивати трошкови."</string>
+ <string name="network_switch_metered_toast" msgid="501662047275723743">"Прешли сте са типа мреже <xliff:g id="PREVIOUS_NETWORK">%1$s</xliff:g> на тип мреже <xliff:g id="NEW_NETWORK">%2$s</xliff:g>"</string>
<string-array name="network_switch_type_name">
- <item msgid="2255670471736226365">"mobilni podaci"</item>
+ <item msgid="2255670471736226365">"мобилни подаци"</item>
<item msgid="5520925862115353992">"WiFi"</item>
<item msgid="1055487873974272842">"Bluetooth"</item>
- <item msgid="1616528372438698248">"Eternet"</item>
+ <item msgid="1616528372438698248">"Етернет"</item>
<item msgid="9177085807664964627">"VPN"</item>
</string-array>
- <string name="network_switch_type_name_unknown" msgid="3665696841646851068">"nepoznat tip mreže"</string>
- <string name="accept" msgid="5447154347815825107">"Prihvati"</string>
- <string name="decline" msgid="6490507610282145874">"Odbij"</string>
- <string name="select_character" msgid="3352797107930786979">"Umetanje znaka"</string>
- <string name="sms_control_title" msgid="4748684259903148341">"Slanje SMS poruka"</string>
- <string name="sms_control_message" msgid="6574313876316388239">"<b><xliff:g id="APP_NAME">%1$s</xliff:g></b> šalje veliki broj SMS poruka. Želite li da dozvolite ovoj aplikaciji da nastavi sa slanjem poruka?"</string>
- <string name="sms_control_yes" msgid="4858845109269524622">"Dozvoli"</string>
- <string name="sms_control_no" msgid="4845717880040355570">"Odbij"</string>
- <string name="sms_short_code_confirm_message" msgid="1385416688897538724">"<b><xliff:g id="APP_NAME">%1$s</xliff:g></b> želi da pošalje poruku na adresu <b><xliff:g id="DEST_ADDRESS">%2$s</xliff:g></b>."</string>
- <string name="sms_short_code_details" msgid="2723725738333388351">"Ovo "<b>"može da prouzrokuje troškove"</b>" na računu za mobilni uređaj."</string>
- <string name="sms_premium_short_code_details" msgid="1400296309866638111"><b>"Ovo će prouzrokovati troškove na računu za mobilni uređaj."</b></string>
- <string name="sms_short_code_confirm_allow" msgid="920477594325526691">"Pošalji"</string>
- <string name="sms_short_code_confirm_deny" msgid="1356917469323768230">"Otkaži"</string>
- <string name="sms_short_code_remember_choice" msgid="1374526438647744862">"Zapamti moj izbor"</string>
- <string name="sms_short_code_remember_undo_instruction" msgid="2620984439143080410">"Ovo možete da promenite kasnije u Podešavanja > Aplikacije"</string>
- <string name="sms_short_code_confirm_always_allow" msgid="2223014893129755950">"Uvek dozvoli"</string>
- <string name="sms_short_code_confirm_never_allow" msgid="2688828813521652079">"Nikada ne dozvoli"</string>
+ <string name="network_switch_type_name_unknown" msgid="3665696841646851068">"непознат тип мреже"</string>
+ <string name="accept" msgid="5447154347815825107">"Прихвати"</string>
+ <string name="decline" msgid="6490507610282145874">"Одбиј"</string>
+ <string name="select_character" msgid="3352797107930786979">"Уметање знака"</string>
+ <string name="sms_control_title" msgid="4748684259903148341">"Слање SMS порука"</string>
+ <string name="sms_control_message" msgid="6574313876316388239">"<b><xliff:g id="APP_NAME">%1$s</xliff:g></b> шаље велики број SMS порука. Желите ли да дозволите овој апликацији да настави са слањем порука?"</string>
+ <string name="sms_control_yes" msgid="4858845109269524622">"Дозволи"</string>
+ <string name="sms_control_no" msgid="4845717880040355570">"Одбиј"</string>
+ <string name="sms_short_code_confirm_message" msgid="1385416688897538724">"<b><xliff:g id="APP_NAME">%1$s</xliff:g></b> жели да пошаље поруку на адресу <b><xliff:g id="DEST_ADDRESS">%2$s</xliff:g></b>."</string>
+ <string name="sms_short_code_details" msgid="2723725738333388351">"Ово "<b>"може да проузрокује трошкове"</b>" на рачуну за мобилни уређај."</string>
+ <string name="sms_premium_short_code_details" msgid="1400296309866638111"><b>"Ово ће проузроковати трошкове на рачуну за мобилни уређај."</b></string>
+ <string name="sms_short_code_confirm_allow" msgid="920477594325526691">"Пошаљи"</string>
+ <string name="sms_short_code_confirm_deny" msgid="1356917469323768230">"Откажи"</string>
+ <string name="sms_short_code_remember_choice" msgid="1374526438647744862">"Запамти мој избор"</string>
+ <string name="sms_short_code_remember_undo_instruction" msgid="2620984439143080410">"Ово можете да промените касније у Подешавања > Апликације"</string>
+ <string name="sms_short_code_confirm_always_allow" msgid="2223014893129755950">"Увек дозволи"</string>
+ <string name="sms_short_code_confirm_never_allow" msgid="2688828813521652079">"Никада не дозволи"</string>
<!-- no translation found for sim_removed_title (1349026474932481037) -->
<skip />
<!-- no translation found for sim_removed_message (8469588437451533845) -->
<skip />
- <string name="sim_done_button" msgid="6464250841528410598">"Gotovo"</string>
+ <string name="sim_done_button" msgid="6464250841528410598">"Готово"</string>
<!-- no translation found for sim_added_title (2976783426741012468) -->
<skip />
- <string name="sim_added_message" msgid="6602906609509958680">"Restartujte uređaj da biste mogli da pristupite mobilnoj mreži."</string>
- <string name="sim_restart_button" msgid="8481803851341190038">"Restartuj"</string>
- <string name="install_carrier_app_notification_title" msgid="5712723402213090102">"Aktivirajte mobilnu uslugu"</string>
- <string name="install_carrier_app_notification_text" msgid="2781317581274192728">"Preuzmite aplikaciju mobilnog operatera da biste aktivirali novi SIM"</string>
- <string name="install_carrier_app_notification_text_app_name" msgid="4086877327264106484">"Preuzmite aplikaciju <xliff:g id="APP_NAME">%1$s</xliff:g> da biste aktivirali novu SIM karticu"</string>
- <string name="install_carrier_app_notification_button" msgid="6257740533102594290">"Preuzmite aplikaciju"</string>
- <string name="carrier_app_notification_title" msgid="5815477368072060250">"Nova SIM kartica je umetnuta"</string>
- <string name="carrier_app_notification_text" msgid="6567057546341958637">"Dodirnite za podešavanje"</string>
- <string name="time_picker_dialog_title" msgid="9053376764985220821">"Podesite vreme"</string>
- <string name="date_picker_dialog_title" msgid="5030520449243071926">"Podešavanje datuma"</string>
- <string name="date_time_set" msgid="4603445265164486816">"Podesi"</string>
- <string name="date_time_done" msgid="8363155889402873463">"Gotovo"</string>
- <string name="perms_new_perm_prefix" msgid="6984556020395757087"><font size="12" fgcolor="#ff33b5e5">"NOVO: "</font></string>
- <string name="perms_description_app" msgid="2747752389870161996">"Omogućava <xliff:g id="APP_NAME">%1$s</xliff:g>."</string>
- <string name="no_permissions" msgid="5729199278862516390">"Nije potrebna nijedna dozvola"</string>
- <string name="perm_costs_money" msgid="749054595022779685">"ovo će vam možda biti naplaćeno"</string>
- <string name="dlg_ok" msgid="5103447663504839312">"Potvrdi"</string>
- <string name="usb_charging_notification_title" msgid="1674124518282666955">"Ovaj uređaj se puni preko USB-a"</string>
- <string name="usb_supplying_notification_title" msgid="5378546632408101811">"Povezani uređaj se puni preko USB-a"</string>
- <string name="usb_mtp_notification_title" msgid="1065989144124499810">"USB prenos datoteka je uključen"</string>
- <string name="usb_ptp_notification_title" msgid="5043437571863443281">"Režim PTP preko USB-a je uključen"</string>
- <string name="usb_tether_notification_title" msgid="8828527870612663771">"USB privezivanje je uključeno"</string>
- <string name="usb_midi_notification_title" msgid="7404506788950595557">"Režim MIDI preko USB-a je uključen"</string>
- <string name="usb_accessory_notification_title" msgid="1385394660861956980">"USB dodatak je povezan"</string>
- <string name="usb_notification_message" msgid="4715163067192110676">"Dodirnite za još opcija."</string>
- <string name="usb_power_notification_message" msgid="7284765627437897702">"Povezani uređaj se puni. Dodirnite za još opcija."</string>
- <string name="usb_unsupported_audio_accessory_title" msgid="2335775548086533065">"Otkrivena je analogna dodatna oprema za audio sadržaj"</string>
- <string name="usb_unsupported_audio_accessory_message" msgid="1300168007129796621">"Priključeni uređaj nije kompatibilan sa ovim telefonom. Dodirnite da biste saznali više."</string>
- <string name="adb_active_notification_title" msgid="408390247354560331">"Povezano je otklanjanje grešaka sa USB-a"</string>
- <string name="adb_active_notification_message" msgid="5617264033476778211">"Dodirnite da biste ga isključili"</string>
- <string name="adb_active_notification_message" product="tv" msgid="6624498401272780855">"Izaberite da biste onemogućili otklanjanja grešaka sa USB-a."</string>
- <string name="adbwifi_active_notification_title" msgid="6147343659168302473">"Bežično otklanjanje grešaka je povezano"</string>
- <string name="adbwifi_active_notification_message" msgid="930987922852867972">"Dodirnite da biste isključili bežično otklanjanje grešaka"</string>
- <string name="adbwifi_active_notification_message" product="tv" msgid="8633421848366915478">"Izaberite da biste onemogućili bežično otklanjanje grešaka."</string>
- <string name="test_harness_mode_notification_title" msgid="2282785860014142511">"Omogućen je režim probnog korišćenja"</string>
- <string name="test_harness_mode_notification_message" msgid="3039123743127958420">"Obavite resetovanje na fabrička podešavanja da biste onemogućili režim probnog korišćenja."</string>
- <string name="console_running_notification_title" msgid="6087888939261635904">"Serijska konzola je omogućena"</string>
- <string name="console_running_notification_message" msgid="7892751888125174039">"Performanse su smanjene. Da biste onemogući konzolu, proverite pokretački program."</string>
- <string name="mte_override_notification_title" msgid="4731115381962792944">"Eksperimentalni MTE je omogućen"</string>
- <string name="mte_override_notification_message" msgid="2441170442725738942">"Ovo može da utiče na performanse i stabilnost. Restartujte da biste onemogućili. Ako je omogućeno pomoću arm64.memtag.bootctl, prvo podesite na Ništa."</string>
- <string name="usb_contaminant_detected_title" msgid="4359048603069159678">"Tečnost ili nečistoća u USB portu"</string>
- <string name="usb_contaminant_detected_message" msgid="7346100585390795743">"USB port je automatski isključen. Dodirnite da biste saznali više."</string>
- <string name="usb_contaminant_not_detected_title" msgid="2651167729563264053">"Korišćenje USB porta je dozvoljeno"</string>
- <string name="usb_contaminant_not_detected_message" msgid="892863190942660462">"Telefon više ne otkriva tečnost ili nečistoću."</string>
- <string name="taking_remote_bugreport_notification_title" msgid="1582531382166919850">"Izveštaj o grešci se generiše…"</string>
- <string name="share_remote_bugreport_notification_title" msgid="6708897723753334999">"Želite li da podelite izveštaj o grešci?"</string>
- <string name="sharing_remote_bugreport_notification_title" msgid="3077385149217638550">"Deli se izveštaj o grešci…"</string>
- <string name="share_remote_bugreport_notification_message_finished" msgid="7325635795739260135">"Administrator je zatražio izveštaj o grešci radi lakšeg rešavanja problema u vezi sa ovim uređajem. Aplikacije i podaci mogu da se dele."</string>
- <string name="share_remote_bugreport_action" msgid="7630880678785123682">"DELI"</string>
- <string name="decline_remote_bugreport_action" msgid="4040894777519784346">"ODBIJ"</string>
- <string name="select_input_method" msgid="3971267998568587025">"Izbor metoda unosa"</string>
- <string name="show_ime" msgid="6406112007347443383">"Zadržava se na ekranu dok je fizička tastatura aktivna"</string>
- <string name="hardware" msgid="1800597768237606953">"Prikaži virtuelnu tastaturu"</string>
- <string name="select_keyboard_layout_notification_title" msgid="4427643867639774118">"Konfigurišite fizičku tastaturu"</string>
- <string name="select_keyboard_layout_notification_message" msgid="8835158247369158154">"Dodirnite da biste izabrali jezik i raspored"</string>
+ <string name="sim_added_message" msgid="6602906609509958680">"Рестартујте уређај да бисте могли да приступите мобилној мрежи."</string>
+ <string name="sim_restart_button" msgid="8481803851341190038">"Рестартуј"</string>
+ <string name="install_carrier_app_notification_title" msgid="5712723402213090102">"Активирајте мобилну услугу"</string>
+ <string name="install_carrier_app_notification_text" msgid="2781317581274192728">"Преузмите апликацију мобилног оператера да бисте активирали нови SIM"</string>
+ <string name="install_carrier_app_notification_text_app_name" msgid="4086877327264106484">"Преузмите апликацију <xliff:g id="APP_NAME">%1$s</xliff:g> да бисте активирали нову SIM картицу"</string>
+ <string name="install_carrier_app_notification_button" msgid="6257740533102594290">"Преузмите апликацију"</string>
+ <string name="carrier_app_notification_title" msgid="5815477368072060250">"Нова SIM картица је уметнута"</string>
+ <string name="carrier_app_notification_text" msgid="6567057546341958637">"Додирните за подешавање"</string>
+ <string name="time_picker_dialog_title" msgid="9053376764985220821">"Подесите време"</string>
+ <string name="date_picker_dialog_title" msgid="5030520449243071926">"Подешавање датума"</string>
+ <string name="date_time_set" msgid="4603445265164486816">"Подеси"</string>
+ <string name="date_time_done" msgid="8363155889402873463">"Готово"</string>
+ <string name="perms_new_perm_prefix" msgid="6984556020395757087"><font size="12" fgcolor="#ff33b5e5">"НОВО: "</font></string>
+ <string name="perms_description_app" msgid="2747752389870161996">"Омогућава <xliff:g id="APP_NAME">%1$s</xliff:g>."</string>
+ <string name="no_permissions" msgid="5729199278862516390">"Није потребна ниједна дозвола"</string>
+ <string name="perm_costs_money" msgid="749054595022779685">"ово ће вам можда бити наплаћено"</string>
+ <string name="dlg_ok" msgid="5103447663504839312">"Потврди"</string>
+ <string name="usb_charging_notification_title" msgid="1674124518282666955">"Овај уређај се пуни преко USB-а"</string>
+ <string name="usb_supplying_notification_title" msgid="5378546632408101811">"Повезани уређај се пуни преко USB-а"</string>
+ <string name="usb_mtp_notification_title" msgid="1065989144124499810">"USB пренос датотека је укључен"</string>
+ <string name="usb_ptp_notification_title" msgid="5043437571863443281">"Режим PTP преко USB-а је укључен"</string>
+ <string name="usb_tether_notification_title" msgid="8828527870612663771">"USB привезивање је укључено"</string>
+ <string name="usb_midi_notification_title" msgid="7404506788950595557">"Режим MIDI преко USB-а је укључен"</string>
+ <string name="usb_accessory_notification_title" msgid="1385394660861956980">"USB додатак је повезан"</string>
+ <string name="usb_notification_message" msgid="4715163067192110676">"Додирните за још опција."</string>
+ <string name="usb_power_notification_message" msgid="7284765627437897702">"Повезани уређај се пуни. Додирните за још опција."</string>
+ <string name="usb_unsupported_audio_accessory_title" msgid="2335775548086533065">"Откривена је аналогна додатна опрема за аудио садржај"</string>
+ <string name="usb_unsupported_audio_accessory_message" msgid="1300168007129796621">"Прикључени уређај није компатибилан са овим телефоном. Додирните да бисте сазнали више."</string>
+ <string name="adb_active_notification_title" msgid="408390247354560331">"Повезано је отклањање грешака са USB-а"</string>
+ <string name="adb_active_notification_message" msgid="5617264033476778211">"Додирните да бисте га искључили"</string>
+ <string name="adb_active_notification_message" product="tv" msgid="6624498401272780855">"Изаберите да бисте онемогућили отклањања грешака са USB-а."</string>
+ <string name="adbwifi_active_notification_title" msgid="6147343659168302473">"Бежично отклањање грешака је повезано"</string>
+ <string name="adbwifi_active_notification_message" msgid="930987922852867972">"Додирните да бисте искључили бежично отклањање грешака"</string>
+ <string name="adbwifi_active_notification_message" product="tv" msgid="8633421848366915478">"Изаберите да бисте онемогућили бежично отклањање грешака."</string>
+ <string name="test_harness_mode_notification_title" msgid="2282785860014142511">"Омогућен је режим пробног коришћења"</string>
+ <string name="test_harness_mode_notification_message" msgid="3039123743127958420">"Обавите ресетовање на фабричка подешавања да бисте онемогућили режим пробног коришћења."</string>
+ <string name="console_running_notification_title" msgid="6087888939261635904">"Серијска конзола је омогућена"</string>
+ <string name="console_running_notification_message" msgid="7892751888125174039">"Перформансе су смањене. Да бисте онемогући конзолу, проверите покретачки програм."</string>
+ <string name="mte_override_notification_title" msgid="4731115381962792944">"Експериментални MTE је омогућен"</string>
+ <string name="mte_override_notification_message" msgid="2441170442725738942">"Ово може да утиче на перформансе и стабилност. Рестартујте да бисте онемогућили. Ако је омогућено помоћу arm64.memtag.bootctl, прво подесите на Ништа."</string>
+ <string name="usb_contaminant_detected_title" msgid="4359048603069159678">"Течност или нечистоћа у USB порту"</string>
+ <string name="usb_contaminant_detected_message" msgid="7346100585390795743">"USB порт је аутоматски искључен. Додирните да бисте сазнали више."</string>
+ <string name="usb_contaminant_not_detected_title" msgid="2651167729563264053">"Коришћење USB порта је дозвољено"</string>
+ <string name="usb_contaminant_not_detected_message" msgid="892863190942660462">"Телефон више не открива течност или нечистоћу."</string>
+ <string name="taking_remote_bugreport_notification_title" msgid="1582531382166919850">"Извештај о грешци се генерише…"</string>
+ <string name="share_remote_bugreport_notification_title" msgid="6708897723753334999">"Желите ли да поделите извештај о грешци?"</string>
+ <string name="sharing_remote_bugreport_notification_title" msgid="3077385149217638550">"Дели се извештај о грешци…"</string>
+ <string name="share_remote_bugreport_notification_message_finished" msgid="7325635795739260135">"Администратор је затражио извештај о грешци ради лакшег решавања проблема у вези са овим уређајем. Апликације и подаци могу да се деле."</string>
+ <string name="share_remote_bugreport_action" msgid="7630880678785123682">"ДЕЛИ"</string>
+ <string name="decline_remote_bugreport_action" msgid="4040894777519784346">"ОДБИЈ"</string>
+ <string name="select_input_method" msgid="3971267998568587025">"Избор метода уноса"</string>
+ <string name="show_ime" msgid="6406112007347443383">"Задржава се на екрану док је физичка тастатура активна"</string>
+ <string name="hardware" msgid="1800597768237606953">"Прикажи виртуелну тастатуру"</string>
+ <string name="select_keyboard_layout_notification_title" msgid="4427643867639774118">"Конфигуришите физичку тастатуру"</string>
+ <string name="select_keyboard_layout_notification_message" msgid="8835158247369158154">"Додирните да бисте изабрали језик и распоред"</string>
<string name="fast_scroll_alphabet" msgid="8854435958703888376">" ABCDEFGHIJKLMNOPQRSTUVWXYZ"</string>
<string name="fast_scroll_numeric_alphabet" msgid="2529539945421557329">" 0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"</string>
- <string name="alert_windows_notification_channel_group_name" msgid="6063891141815714246">"Prikaz preko drugih aplikacija"</string>
- <string name="alert_windows_notification_channel_name" msgid="3437528564303192620">"Aplikacija <xliff:g id="NAME">%s</xliff:g> se prikazuje preko drugih aplikacija"</string>
- <string name="alert_windows_notification_title" msgid="6331662751095228536">"<xliff:g id="NAME">%s</xliff:g> se prikazuje preko drugih apl."</string>
- <string name="alert_windows_notification_message" msgid="6538171456970725333">"Ako ne želite ovu funkciju za <xliff:g id="NAME">%s</xliff:g>, dodirnite da biste otvorili podešavanja i isključili je."</string>
- <string name="alert_windows_notification_turn_off_action" msgid="7805857234839123780">"Isključi"</string>
- <string name="ext_media_checking_notification_title" msgid="8299199995416510094">"Proverava se <xliff:g id="NAME">%s</xliff:g>…"</string>
- <string name="ext_media_checking_notification_message" msgid="2231566971425375542">"Pregleda se aktuelni sadržaj"</string>
- <string name="ext_media_checking_notification_message" product="tv" msgid="7986154434946021415">"Analizira se memorijski prostor za medije"</string>
- <string name="ext_media_new_notification_title" msgid="3517407571407687677">"Novi/a <xliff:g id="NAME">%s</xliff:g>"</string>
- <string name="ext_media_new_notification_title" product="automotive" msgid="9085349544984742727">"<xliff:g id="NAME">%s</xliff:g> ne radi"</string>
- <string name="ext_media_new_notification_message" msgid="6095403121990786986">"Dodirnite da biste podesili"</string>
- <string name="ext_media_new_notification_message" product="tv" msgid="216863352100263668">"Izaberite da biste podesili"</string>
- <string name="ext_media_new_notification_message" product="automotive" msgid="5140127881613227162">"Možda morate da reformatirate uređaj. Dodirnite da biste izbacili."</string>
- <string name="ext_media_ready_notification_message" msgid="7509496364380197369">"Za čuvanje slika, video snimaka, muzike i drugog sadržaja"</string>
- <string name="ext_media_ready_notification_message" product="tv" msgid="8847134811163165935">"Pregledajte medijske fajlove"</string>
- <string name="ext_media_unmountable_notification_title" msgid="4895444667278979910">"Problem sa: <xliff:g id="NAME">%s</xliff:g>"</string>
- <string name="ext_media_unmountable_notification_title" product="automotive" msgid="3142723758949023280">"<xliff:g id="NAME">%s</xliff:g> ne radi"</string>
- <string name="ext_media_unmountable_notification_message" msgid="3256290114063126205">"Dodirnite da biste ispravili"</string>
- <string name="ext_media_unmountable_notification_message" product="tv" msgid="3003611129979934633">"Medij <xliff:g id="NAME">%s</xliff:g> je oštećen. Izaberite da ga popravite."</string>
- <string name="ext_media_unmountable_notification_message" product="automotive" msgid="2274596120715020680">"Možda morate da reformatirate uređaj. Dodirnite da biste izbacili."</string>
- <string name="ext_media_unsupported_notification_title" msgid="3487534182861251401">"Otkriveno: <xliff:g id="NAME">%s</xliff:g>"</string>
- <string name="ext_media_unsupported_notification_title" product="automotive" msgid="6004193172658722381">"<xliff:g id="NAME">%s</xliff:g> ne radi"</string>
- <string name="ext_media_unsupported_notification_message" msgid="8463636521459807981">"Dodirnite da biste podesili."</string>
- <string name="ext_media_unsupported_notification_message" product="tv" msgid="1595482802187036532">"Izaberite da biste podesili uređaj <xliff:g id="NAME">%s</xliff:g> u podržanom formatu."</string>
- <string name="ext_media_unsupported_notification_message" product="automotive" msgid="3412494732736336330">"Možda morate da reformatirate uređaj"</string>
- <string name="ext_media_badremoval_notification_title" msgid="4114625551266196872">"Uređaj <xliff:g id="NAME">%s</xliff:g> je neočekivano uklonjen"</string>
- <string name="ext_media_badremoval_notification_message" msgid="1986514704499809244">"Izbacite medijum pre nego što ga uklonite da ne biste izgubili sadržaj"</string>
- <string name="ext_media_nomedia_notification_title" msgid="742671636376975890">"<xliff:g id="NAME">%s</xliff:g> je uklonjen/a"</string>
- <string name="ext_media_nomedia_notification_message" msgid="2832724384636625852">"Neke funkcije možda neće ispravno raditi. Umetnite nov memorijski uređaj."</string>
- <string name="ext_media_unmounting_notification_title" msgid="4147986383917892162">"Izbacuje se <xliff:g id="NAME">%s</xliff:g>"</string>
- <string name="ext_media_unmounting_notification_message" msgid="5717036261538754203">"Ne uklanjajte"</string>
- <string name="ext_media_init_action" msgid="2312974060585056709">"Aktiviraj"</string>
- <string name="ext_media_unmount_action" msgid="966992232088442745">"Izbaci"</string>
- <string name="ext_media_browse_action" msgid="344865351947079139">"Istraži"</string>
- <string name="ext_media_seamless_action" msgid="8837030226009268080">"Promenite izlaz"</string>
- <string name="ext_media_missing_title" msgid="3209472091220515046">"<xliff:g id="NAME">%s</xliff:g> nedostaje"</string>
- <string name="ext_media_missing_message" msgid="4408988706227922909">"Ponovo umetnite uređaj"</string>
- <string name="ext_media_move_specific_title" msgid="8492118544775964250">"Prenosi se <xliff:g id="NAME">%s</xliff:g>"</string>
- <string name="ext_media_move_title" msgid="2682741525619033637">"Podaci se prenose"</string>
- <string name="ext_media_move_success_title" msgid="4901763082647316767">"Prenos sadržaja je gotov"</string>
- <string name="ext_media_move_success_message" msgid="9159542002276982979">"Sadržaj je premešen na: <xliff:g id="NAME">%s</xliff:g>"</string>
- <string name="ext_media_move_failure_title" msgid="3184577479181333665">"Premeštanje sadržaja nije uspelo"</string>
- <string name="ext_media_move_failure_message" msgid="4197306718121869335">"Probajte da ponovo premestite sadržaj"</string>
- <string name="ext_media_status_removed" msgid="241223931135751691">"Uklonjen je"</string>
- <string name="ext_media_status_unmounted" msgid="8145812017295835941">"Izbačen je"</string>
- <string name="ext_media_status_checking" msgid="159013362442090347">"Proverava se..."</string>
- <string name="ext_media_status_mounted" msgid="3459448555811203459">"Spreman je"</string>
- <string name="ext_media_status_mounted_ro" msgid="1974809199760086956">"Samo za čitanje"</string>
- <string name="ext_media_status_bad_removal" msgid="508448566481406245">"Uklonjen je na nebezbedan način"</string>
- <string name="ext_media_status_unmountable" msgid="7043574843541087748">"Oštećen je"</string>
- <string name="ext_media_status_unsupported" msgid="5460509911660539317">"Nije podržan"</string>
- <string name="ext_media_status_ejecting" msgid="7532403368044013797">"Izbacuje se..."</string>
- <string name="ext_media_status_formatting" msgid="774148701503179906">"Formatira se..."</string>
- <string name="ext_media_status_missing" msgid="6520746443048867314">"Nije umetnut"</string>
- <string name="activity_list_empty" msgid="4219430010716034252">"Nije pronađena nijedna podudarna aktivnost."</string>
- <string name="permlab_route_media_output" msgid="8048124531439513118">"usmeravanje izlaza medija"</string>
- <string name="permdesc_route_media_output" msgid="1759683269387729675">"Dozvoljava aplikaciji da usmerava izlaz medija na druge spoljne uređaje."</string>
- <string name="permlab_readInstallSessions" msgid="7279049337895583621">"čitanje sesija instaliranja"</string>
- <string name="permdesc_readInstallSessions" msgid="4012608316610763473">"Dozvoljava aplikaciji da čita sesije instaliranja. To joj dozvoljava da vidi detalje o aktivnim instalacijama paketa."</string>
- <string name="permlab_requestInstallPackages" msgid="7600020863445351154">"zahtevanje paketa za instaliranje"</string>
- <string name="permdesc_requestInstallPackages" msgid="3969369278325313067">"Omogućava da aplikacija zahteva instalaciju paketa."</string>
- <string name="permlab_requestDeletePackages" msgid="2541172829260106795">"zahtevanje brisanja paketa"</string>
- <string name="permdesc_requestDeletePackages" msgid="6133633516423860381">"Omogućava da aplikacija zahteva brisanje paketa."</string>
- <string name="permlab_requestIgnoreBatteryOptimizations" msgid="7646611326036631439">"traženje dozvole za ignorisanje optimizacija baterije"</string>
- <string name="permdesc_requestIgnoreBatteryOptimizations" msgid="634260656917874356">"Dozvoljava aplikaciji da traži dozvolu za ignorisanje optimizacija baterije za tu aplikaciju."</string>
- <string name="permlab_queryAllPackages" msgid="2928450604653281650">"slanje upita za sve pakete"</string>
- <string name="permdesc_queryAllPackages" msgid="5339069855520996010">"Dozvoljava aplikaciji da vidi sve instalirane pakete."</string>
- <string name="tutorial_double_tap_to_zoom_message_short" msgid="1842872462124648678">"Dodirnite dvaput za kontrolu zumiranja"</string>
- <string name="gadget_host_error_inflating" msgid="2449961590495198720">"Nije moguće dodati vidžet."</string>
- <string name="ime_action_go" msgid="5536744546326495436">"Idi"</string>
- <string name="ime_action_search" msgid="4501435960587287668">"Pretraži"</string>
- <string name="ime_action_send" msgid="8456843745664334138">"Pošalji"</string>
- <string name="ime_action_next" msgid="4169702997635728543">"Dalje"</string>
- <string name="ime_action_done" msgid="6299921014822891569">"Gotovo"</string>
- <string name="ime_action_previous" msgid="6548799326860401611">"Prethodno"</string>
- <string name="ime_action_default" msgid="8265027027659800121">"Izvrši"</string>
- <string name="dial_number_using" msgid="6060769078933953531">"Biraj broj\nkoristeći <xliff:g id="NUMBER">%s</xliff:g>"</string>
- <string name="create_contact_using" msgid="6200708808003692594">"Napravite kontakt\nkoristeći <xliff:g id="NUMBER">%s</xliff:g>"</string>
- <string name="grant_credentials_permission_message_header" msgid="5365733888842570481">"Sledeće aplikacije zahtevaju dozvolu za pristup nalogu, kako sada, tako i ubuduće."</string>
- <string name="grant_credentials_permission_message_footer" msgid="1886710210516246461">"Želite da odobrite ovaj zahtev?"</string>
- <string name="grant_permissions_header_text" msgid="3420736827804657201">"Zahtev za pristup"</string>
- <string name="allow" msgid="6195617008611933762">"Dozvoli"</string>
- <string name="deny" msgid="6632259981847676572">"Odbij"</string>
- <string name="permission_request_notification_title" msgid="1810025922441048273">"Zatražena je dozvola"</string>
- <string name="permission_request_notification_with_subtitle" msgid="3743417870360129298">"Zatražena je dozvola\nza nalog <xliff:g id="ACCOUNT">%s</xliff:g>"</string>
- <string name="permission_request_notification_for_app_with_subtitle" msgid="1298704005732851350">"<xliff:g id="APP">%1$s</xliff:g> traži dozvolu \nza nalog <xliff:g id="ACCOUNT">%2$s</xliff:g>."</string>
- <string name="forward_intent_to_owner" msgid="4620359037192871015">"Koristite ovu aplikaciju izvan poslovnog profila"</string>
- <string name="forward_intent_to_work" msgid="3620262405636021151">"Koristite ovu aplikaciju na poslovnom profilu"</string>
- <string name="input_method_binding_label" msgid="1166731601721983656">"Metod unosa"</string>
- <string name="sync_binding_label" msgid="469249309424662147">"Sinhronizacija"</string>
- <string name="accessibility_binding_label" msgid="1974602776545801715">"Pristupačnost"</string>
- <string name="wallpaper_binding_label" msgid="1197440498000786738">"Pozadina"</string>
- <string name="chooser_wallpaper" msgid="3082405680079923708">"Promena pozadine"</string>
- <string name="notification_listener_binding_label" msgid="2702165274471499713">"Monitor obaveštenja"</string>
- <string name="vr_listener_binding_label" msgid="8013112996671206429">"Obrađivač za virtuelnu realnost"</string>
- <string name="condition_provider_service_binding_label" msgid="8490641013951857673">"Dobavljač uslova"</string>
- <string name="notification_ranker_binding_label" msgid="432708245635563763">"Usluga rangiranja obaveštenja"</string>
- <string name="vpn_title" msgid="5906991595291514182">"VPN je aktiviran"</string>
- <string name="vpn_title_long" msgid="6834144390504619998">"Aplikacija <xliff:g id="APP">%s</xliff:g> je aktivirala VPN"</string>
- <string name="vpn_text" msgid="2275388920267251078">"Dodirnite da biste upravljali mrežom."</string>
- <string name="vpn_text_long" msgid="278540576806169831">"Povezano sa sesijom <xliff:g id="SESSION">%s</xliff:g>. Dodirnite da biste upravljali mrežom."</string>
- <string name="vpn_lockdown_connecting" msgid="6096725311950342607">"Povezivanje stalno uključenog VPN-a..."</string>
- <string name="vpn_lockdown_connected" msgid="2853127976590658469">"Stalno uključen VPN je povezan"</string>
- <string name="vpn_lockdown_disconnected" msgid="5573611651300764955">"Veza sa uvek uključenim VPN-om je prekinuta"</string>
- <string name="vpn_lockdown_error" msgid="4453048646854247947">"Povezivanje na stalno uključeni VPN nije uspelo"</string>
- <string name="vpn_lockdown_config" msgid="8331697329868252169">"Promenite podešavanja VPN-a"</string>
- <string name="upload_file" msgid="8651942222301634271">"Odaberi fajl"</string>
- <string name="no_file_chosen" msgid="4146295695162318057">"Nije izabrana nijedna datoteka"</string>
- <string name="reset" msgid="3865826612628171429">"Resetuj"</string>
- <string name="submit" msgid="862795280643405865">"Pošalji"</string>
- <string name="car_mode_disable_notification_title" msgid="8450693275833142896">"Aplikacija za vožnju je pokrenuta"</string>
- <string name="car_mode_disable_notification_message" msgid="8954550232288567515">"Dodirnite da biste izašli iz aplikacije za vožnju."</string>
- <string name="back_button_label" msgid="4078224038025043387">"Nazad"</string>
+ <string name="alert_windows_notification_channel_group_name" msgid="6063891141815714246">"Приказ преко других апликација"</string>
+ <string name="alert_windows_notification_channel_name" msgid="3437528564303192620">"Апликација <xliff:g id="NAME">%s</xliff:g> се приказује преко других апликација"</string>
+ <string name="alert_windows_notification_title" msgid="6331662751095228536">"<xliff:g id="NAME">%s</xliff:g> се приказује преко других апл."</string>
+ <string name="alert_windows_notification_message" msgid="6538171456970725333">"Ако не желите ову функцију за <xliff:g id="NAME">%s</xliff:g>, додирните да бисте отворили подешавања и искључили је."</string>
+ <string name="alert_windows_notification_turn_off_action" msgid="7805857234839123780">"Искључи"</string>
+ <string name="ext_media_checking_notification_title" msgid="8299199995416510094">"Проверава се <xliff:g id="NAME">%s</xliff:g>…"</string>
+ <string name="ext_media_checking_notification_message" msgid="2231566971425375542">"Прегледа се актуелни садржај"</string>
+ <string name="ext_media_checking_notification_message" product="tv" msgid="7986154434946021415">"Анализира се меморијски простор за медије"</string>
+ <string name="ext_media_new_notification_title" msgid="3517407571407687677">"Нови/а <xliff:g id="NAME">%s</xliff:g>"</string>
+ <string name="ext_media_new_notification_title" product="automotive" msgid="9085349544984742727">"<xliff:g id="NAME">%s</xliff:g> не ради"</string>
+ <string name="ext_media_new_notification_message" msgid="6095403121990786986">"Додирните да бисте подесили"</string>
+ <string name="ext_media_new_notification_message" product="tv" msgid="216863352100263668">"Изаберите да бисте подесили"</string>
+ <string name="ext_media_new_notification_message" product="automotive" msgid="5140127881613227162">"Можда морате да реформатирате уређај. Додирните да бисте избацили."</string>
+ <string name="ext_media_ready_notification_message" msgid="7509496364380197369">"За чување слика, видео снимака, музике и другог садржаја"</string>
+ <string name="ext_media_ready_notification_message" product="tv" msgid="8847134811163165935">"Прегледајте медијске фајлове"</string>
+ <string name="ext_media_unmountable_notification_title" msgid="4895444667278979910">"Проблем са: <xliff:g id="NAME">%s</xliff:g>"</string>
+ <string name="ext_media_unmountable_notification_title" product="automotive" msgid="3142723758949023280">"<xliff:g id="NAME">%s</xliff:g> не ради"</string>
+ <string name="ext_media_unmountable_notification_message" msgid="3256290114063126205">"Додирните да бисте исправили"</string>
+ <string name="ext_media_unmountable_notification_message" product="tv" msgid="3003611129979934633">"Медиј <xliff:g id="NAME">%s</xliff:g> је оштећен. Изаберите да га поправите."</string>
+ <string name="ext_media_unmountable_notification_message" product="automotive" msgid="2274596120715020680">"Можда морате да реформатирате уређај. Додирните да бисте избацили."</string>
+ <string name="ext_media_unsupported_notification_title" msgid="3487534182861251401">"Откривенo: <xliff:g id="NAME">%s</xliff:g>"</string>
+ <string name="ext_media_unsupported_notification_title" product="automotive" msgid="6004193172658722381">"<xliff:g id="NAME">%s</xliff:g> не ради"</string>
+ <string name="ext_media_unsupported_notification_message" msgid="8463636521459807981">"Додирните да бисте подесили."</string>
+ <string name="ext_media_unsupported_notification_message" product="tv" msgid="1595482802187036532">"Изаберите да бисте подесили уређај <xliff:g id="NAME">%s</xliff:g> у подржаном формату."</string>
+ <string name="ext_media_unsupported_notification_message" product="automotive" msgid="3412494732736336330">"Можда морате да реформатирате уређај"</string>
+ <string name="ext_media_badremoval_notification_title" msgid="4114625551266196872">"Уређај <xliff:g id="NAME">%s</xliff:g> је неочекивано уклоњен"</string>
+ <string name="ext_media_badremoval_notification_message" msgid="1986514704499809244">"Избаците медијум пре него што га уклоните да не бисте изгубили садржај"</string>
+ <string name="ext_media_nomedia_notification_title" msgid="742671636376975890">"<xliff:g id="NAME">%s</xliff:g> је уклоњен/а"</string>
+ <string name="ext_media_nomedia_notification_message" msgid="2832724384636625852">"Неке функције можда неће исправно радити. Уметните нов меморијски уређај."</string>
+ <string name="ext_media_unmounting_notification_title" msgid="4147986383917892162">"Избацује се <xliff:g id="NAME">%s</xliff:g>"</string>
+ <string name="ext_media_unmounting_notification_message" msgid="5717036261538754203">"Не уклањајте"</string>
+ <string name="ext_media_init_action" msgid="2312974060585056709">"Активирај"</string>
+ <string name="ext_media_unmount_action" msgid="966992232088442745">"Избаци"</string>
+ <string name="ext_media_browse_action" msgid="344865351947079139">"Истражи"</string>
+ <string name="ext_media_seamless_action" msgid="8837030226009268080">"Промените излаз"</string>
+ <string name="ext_media_missing_title" msgid="3209472091220515046">"<xliff:g id="NAME">%s</xliff:g> недостаје"</string>
+ <string name="ext_media_missing_message" msgid="4408988706227922909">"Поново уметните уређај"</string>
+ <string name="ext_media_move_specific_title" msgid="8492118544775964250">"Преноси се <xliff:g id="NAME">%s</xliff:g>"</string>
+ <string name="ext_media_move_title" msgid="2682741525619033637">"Подаци се преносе"</string>
+ <string name="ext_media_move_success_title" msgid="4901763082647316767">"Пренос садржаја је готов"</string>
+ <string name="ext_media_move_success_message" msgid="9159542002276982979">"Садржај је премешен на: <xliff:g id="NAME">%s</xliff:g>"</string>
+ <string name="ext_media_move_failure_title" msgid="3184577479181333665">"Премештање садржаја није успело"</string>
+ <string name="ext_media_move_failure_message" msgid="4197306718121869335">"Пробајте да поново преместите садржај"</string>
+ <string name="ext_media_status_removed" msgid="241223931135751691">"Уклоњен је"</string>
+ <string name="ext_media_status_unmounted" msgid="8145812017295835941">"Избачен је"</string>
+ <string name="ext_media_status_checking" msgid="159013362442090347">"Проверава се..."</string>
+ <string name="ext_media_status_mounted" msgid="3459448555811203459">"Спреман је"</string>
+ <string name="ext_media_status_mounted_ro" msgid="1974809199760086956">"Само за читање"</string>
+ <string name="ext_media_status_bad_removal" msgid="508448566481406245">"Уклоњен је на небезбедан начин"</string>
+ <string name="ext_media_status_unmountable" msgid="7043574843541087748">"Оштећен је"</string>
+ <string name="ext_media_status_unsupported" msgid="5460509911660539317">"Није подржан"</string>
+ <string name="ext_media_status_ejecting" msgid="7532403368044013797">"Избацује се..."</string>
+ <string name="ext_media_status_formatting" msgid="774148701503179906">"Форматира се..."</string>
+ <string name="ext_media_status_missing" msgid="6520746443048867314">"Није уметнут"</string>
+ <string name="activity_list_empty" msgid="4219430010716034252">"Није пронађена ниједна подударна активност."</string>
+ <string name="permlab_route_media_output" msgid="8048124531439513118">"усмеравање излаза медија"</string>
+ <string name="permdesc_route_media_output" msgid="1759683269387729675">"Дозвољава апликацији да усмерава излаз медија на друге спољне уређаје."</string>
+ <string name="permlab_readInstallSessions" msgid="7279049337895583621">"читање сесија инсталирања"</string>
+ <string name="permdesc_readInstallSessions" msgid="4012608316610763473">"Дозвољава апликацији да чита сесије инсталирања. То јој дозвољава да види детаље о активним инсталацијама пакета."</string>
+ <string name="permlab_requestInstallPackages" msgid="7600020863445351154">"захтевање пакета за инсталирање"</string>
+ <string name="permdesc_requestInstallPackages" msgid="3969369278325313067">"Омогућава да апликација захтева инсталацију пакета."</string>
+ <string name="permlab_requestDeletePackages" msgid="2541172829260106795">"захтевање брисања пакета"</string>
+ <string name="permdesc_requestDeletePackages" msgid="6133633516423860381">"Омогућава да апликација захтева брисање пакета."</string>
+ <string name="permlab_requestIgnoreBatteryOptimizations" msgid="7646611326036631439">"тражење дозволе за игнорисање оптимизација батерије"</string>
+ <string name="permdesc_requestIgnoreBatteryOptimizations" msgid="634260656917874356">"Дозвољава апликацији да тражи дозволу за игнорисање оптимизација батерије за ту апликацију."</string>
+ <string name="permlab_queryAllPackages" msgid="2928450604653281650">"слање упита за све пакете"</string>
+ <string name="permdesc_queryAllPackages" msgid="5339069855520996010">"Дозвољава апликацији да види све инсталиране пакете."</string>
+ <string name="tutorial_double_tap_to_zoom_message_short" msgid="1842872462124648678">"Додирните двапут за контролу зумирања"</string>
+ <string name="gadget_host_error_inflating" msgid="2449961590495198720">"Није могуће додати виџет."</string>
+ <string name="ime_action_go" msgid="5536744546326495436">"Иди"</string>
+ <string name="ime_action_search" msgid="4501435960587287668">"Претражи"</string>
+ <string name="ime_action_send" msgid="8456843745664334138">"Пошаљи"</string>
+ <string name="ime_action_next" msgid="4169702997635728543">"Даље"</string>
+ <string name="ime_action_done" msgid="6299921014822891569">"Готово"</string>
+ <string name="ime_action_previous" msgid="6548799326860401611">"Претходно"</string>
+ <string name="ime_action_default" msgid="8265027027659800121">"Изврши"</string>
+ <string name="dial_number_using" msgid="6060769078933953531">"Бирај број\nкористећи <xliff:g id="NUMBER">%s</xliff:g>"</string>
+ <string name="create_contact_using" msgid="6200708808003692594">"Направите контакт\nкористећи <xliff:g id="NUMBER">%s</xliff:g>"</string>
+ <string name="grant_credentials_permission_message_header" msgid="5365733888842570481">"Следеће апликације захтевају дозволу за приступ налогу, како сада, тако и убудуће."</string>
+ <string name="grant_credentials_permission_message_footer" msgid="1886710210516246461">"Желите да одобрите овај захтев?"</string>
+ <string name="grant_permissions_header_text" msgid="3420736827804657201">"Захтев за приступ"</string>
+ <string name="allow" msgid="6195617008611933762">"Дозволи"</string>
+ <string name="deny" msgid="6632259981847676572">"Одбиј"</string>
+ <string name="permission_request_notification_title" msgid="1810025922441048273">"Затражена је дозвола"</string>
+ <string name="permission_request_notification_with_subtitle" msgid="3743417870360129298">"Затражена је дозвола\nза налог <xliff:g id="ACCOUNT">%s</xliff:g>"</string>
+ <string name="permission_request_notification_for_app_with_subtitle" msgid="1298704005732851350">"<xliff:g id="APP">%1$s</xliff:g> тражи дозволу \nза налог <xliff:g id="ACCOUNT">%2$s</xliff:g>."</string>
+ <string name="forward_intent_to_owner" msgid="4620359037192871015">"Користите ову апликацију изван пословног профила"</string>
+ <string name="forward_intent_to_work" msgid="3620262405636021151">"Користите ову апликацију на пословном профилу"</string>
+ <string name="input_method_binding_label" msgid="1166731601721983656">"Метод уноса"</string>
+ <string name="sync_binding_label" msgid="469249309424662147">"Синхронизација"</string>
+ <string name="accessibility_binding_label" msgid="1974602776545801715">"Приступачност"</string>
+ <string name="wallpaper_binding_label" msgid="1197440498000786738">"Позадина"</string>
+ <string name="chooser_wallpaper" msgid="3082405680079923708">"Промена позадине"</string>
+ <string name="notification_listener_binding_label" msgid="2702165274471499713">"Монитор обавештења"</string>
+ <string name="vr_listener_binding_label" msgid="8013112996671206429">"Обрађивач за виртуелну реалност"</string>
+ <string name="condition_provider_service_binding_label" msgid="8490641013951857673">"Добављач услова"</string>
+ <string name="notification_ranker_binding_label" msgid="432708245635563763">"Услуга рангирања обавештења"</string>
+ <string name="vpn_title" msgid="5906991595291514182">"VPN је активиран"</string>
+ <string name="vpn_title_long" msgid="6834144390504619998">"Апликација <xliff:g id="APP">%s</xliff:g> је активирала VPN"</string>
+ <string name="vpn_text" msgid="2275388920267251078">"Додирните да бисте управљали мрежом."</string>
+ <string name="vpn_text_long" msgid="278540576806169831">"Повезано са сесијом <xliff:g id="SESSION">%s</xliff:g>. Додирните да бисте управљали мрежом."</string>
+ <string name="vpn_lockdown_connecting" msgid="6096725311950342607">"Повезивање стално укљученог VPN-а..."</string>
+ <string name="vpn_lockdown_connected" msgid="2853127976590658469">"Стално укључен VPN је повезан"</string>
+ <string name="vpn_lockdown_disconnected" msgid="5573611651300764955">"Веза са увек укљученим VPN-ом је прекинута"</string>
+ <string name="vpn_lockdown_error" msgid="4453048646854247947">"Повезивање на стално укључени VPN није успело"</string>
+ <string name="vpn_lockdown_config" msgid="8331697329868252169">"Промените подешавања VPN-а"</string>
+ <string name="upload_file" msgid="8651942222301634271">"Одабери фајл"</string>
+ <string name="no_file_chosen" msgid="4146295695162318057">"Није изабрана ниједна датотека"</string>
+ <string name="reset" msgid="3865826612628171429">"Ресетуј"</string>
+ <string name="submit" msgid="862795280643405865">"Пошаљи"</string>
+ <string name="car_mode_disable_notification_title" msgid="8450693275833142896">"Апликација за вожњу је покренута"</string>
+ <string name="car_mode_disable_notification_message" msgid="8954550232288567515">"Додирните да бисте изашли из апликације за вожњу."</string>
+ <string name="back_button_label" msgid="4078224038025043387">"Назад"</string>
<string name="next_button_label" msgid="6040209156399907780">"Next"</string>
- <string name="skip_button_label" msgid="3566599811326688389">"Preskoči"</string>
- <string name="no_matches" msgid="6472699895759164599">"Nema podudaranja"</string>
- <string name="find_on_page" msgid="5400537367077438198">"Pronađi na stranici"</string>
- <string name="matches_found" msgid="2296462299979507689">"{count,plural, =1{# podudaranje}one{# od {total}}few{# od {total}}other{# od {total}}}"</string>
- <string name="action_mode_done" msgid="2536182504764803222">"Gotovo"</string>
- <string name="progress_erasing" msgid="6891435992721028004">"Briše se deljeni memorijski prostor…"</string>
- <string name="share" msgid="4157615043345227321">"Deli"</string>
- <string name="find" msgid="5015737188624767706">"Pronađi"</string>
- <string name="websearch" msgid="5624340204512793290">"Veb-pretraga"</string>
- <string name="find_next" msgid="5341217051549648153">"Pronađi sledeće"</string>
- <string name="find_previous" msgid="4405898398141275532">"Pronađi prethodno"</string>
- <string name="gpsNotifTicker" msgid="3207361857637620780">"Zahtev za lokaciju od korisnika <xliff:g id="NAME">%s</xliff:g>"</string>
- <string name="gpsNotifTitle" msgid="1590033371665669570">"Zahtev za lokaciju"</string>
- <string name="gpsNotifMessage" msgid="7346649122793758032">"Zahteva <xliff:g id="NAME">%1$s</xliff:g> (<xliff:g id="SERVICE">%2$s</xliff:g>)"</string>
- <string name="gpsVerifYes" msgid="3719843080744112940">"Da"</string>
- <string name="gpsVerifNo" msgid="1671201856091564741">"Ne"</string>
- <string name="sync_too_many_deletes" msgid="6999440774578705300">"Premašeno je ograničenje za brisanje"</string>
- <string name="sync_too_many_deletes_desc" msgid="7409327940303504440">"Postoje izbrisane stavke (<xliff:g id="NUMBER_OF_DELETED_ITEMS">%1$d</xliff:g>) za <xliff:g id="TYPE_OF_SYNC">%2$s</xliff:g>, nalog <xliff:g id="ACCOUNT_NAME">%3$s</xliff:g>. Šta želite da uradite?"</string>
- <string name="sync_really_delete" msgid="5657871730315579051">"Izbriši stavke"</string>
- <string name="sync_undo_deletes" msgid="5786033331266418896">"Opozovi brisanja"</string>
- <string name="sync_do_nothing" msgid="4528734662446469646">"Ne radi ništa za sada"</string>
- <string name="choose_account_label" msgid="5557833752759831548">"Izaberite nalog"</string>
- <string name="add_account_label" msgid="4067610644298737417">"Dodaj nalog"</string>
- <string name="add_account_button_label" msgid="322390749416414097">"Dodaj nalog"</string>
- <string name="number_picker_increment_button" msgid="7621013714795186298">"Povećavanje"</string>
- <string name="number_picker_decrement_button" msgid="5116948444762708204">"Smanjivanje"</string>
- <string name="number_picker_increment_scroll_mode" msgid="8403893549806805985">"<xliff:g id="VALUE">%s</xliff:g> dodirnite i zadržite."</string>
- <string name="number_picker_increment_scroll_action" msgid="8310191318914268271">"Prevucite nagore da biste povećali, a nadole da biste smanjili."</string>
- <string name="time_picker_increment_minute_button" msgid="7195870222945784300">"Povećavanje minuta"</string>
- <string name="time_picker_decrement_minute_button" msgid="230925389943411490">"Smanjivanje minuta"</string>
- <string name="time_picker_increment_hour_button" msgid="3063572723197178242">"Povećavanje sati"</string>
- <string name="time_picker_decrement_hour_button" msgid="584101766855054412">"Smanjivanje sati"</string>
- <string name="time_picker_increment_set_pm_button" msgid="5889149366900376419">"Podesi po podne"</string>
- <string name="time_picker_decrement_set_am_button" msgid="1422608001541064087">"Podesi pre podne"</string>
- <string name="date_picker_increment_month_button" msgid="3447263316096060309">"Povećavanje meseca"</string>
- <string name="date_picker_decrement_month_button" msgid="6531888937036883014">"Smanjivanje meseca"</string>
- <string name="date_picker_increment_day_button" msgid="4349336637188534259">"Povećavanje dana"</string>
- <string name="date_picker_decrement_day_button" msgid="6840253837656637248">"Smanjivanje dana"</string>
- <string name="date_picker_increment_year_button" msgid="7608128783435372594">"Povećavanje godine"</string>
- <string name="date_picker_decrement_year_button" msgid="4102586521754172684">"Smanjivanje godine"</string>
- <string name="date_picker_prev_month_button" msgid="3418694374017868369">"Prethodni mesec"</string>
- <string name="date_picker_next_month_button" msgid="4858207337779144840">"Sledeći mesec"</string>
+ <string name="skip_button_label" msgid="3566599811326688389">"Прескочи"</string>
+ <string name="no_matches" msgid="6472699895759164599">"Нема подударања"</string>
+ <string name="find_on_page" msgid="5400537367077438198">"Пронађи на страници"</string>
+ <string name="matches_found" msgid="2296462299979507689">"{count,plural, =1{# подударање}one{# од {total}}few{# од {total}}other{# од {total}}}"</string>
+ <string name="action_mode_done" msgid="2536182504764803222">"Готово"</string>
+ <string name="progress_erasing" msgid="6891435992721028004">"Брише се дељени меморијски простор…"</string>
+ <string name="share" msgid="4157615043345227321">"Дели"</string>
+ <string name="find" msgid="5015737188624767706">"Пронађи"</string>
+ <string name="websearch" msgid="5624340204512793290">"Веб-претрага"</string>
+ <string name="find_next" msgid="5341217051549648153">"Пронађи следеће"</string>
+ <string name="find_previous" msgid="4405898398141275532">"Пронађи претходно"</string>
+ <string name="gpsNotifTicker" msgid="3207361857637620780">"Захтев за локацију од корисника <xliff:g id="NAME">%s</xliff:g>"</string>
+ <string name="gpsNotifTitle" msgid="1590033371665669570">"Захтев за локацију"</string>
+ <string name="gpsNotifMessage" msgid="7346649122793758032">"Захтева <xliff:g id="NAME">%1$s</xliff:g> (<xliff:g id="SERVICE">%2$s</xliff:g>)"</string>
+ <string name="gpsVerifYes" msgid="3719843080744112940">"Да"</string>
+ <string name="gpsVerifNo" msgid="1671201856091564741">"Не"</string>
+ <string name="sync_too_many_deletes" msgid="6999440774578705300">"Премашено је ограничење за брисање"</string>
+ <string name="sync_too_many_deletes_desc" msgid="7409327940303504440">"Постоје избрисане ставке (<xliff:g id="NUMBER_OF_DELETED_ITEMS">%1$d</xliff:g>) за <xliff:g id="TYPE_OF_SYNC">%2$s</xliff:g>, налог <xliff:g id="ACCOUNT_NAME">%3$s</xliff:g>. Шта желите да урадите?"</string>
+ <string name="sync_really_delete" msgid="5657871730315579051">"Избриши ставке"</string>
+ <string name="sync_undo_deletes" msgid="5786033331266418896">"Опозови брисања"</string>
+ <string name="sync_do_nothing" msgid="4528734662446469646">"Не ради ништа за сада"</string>
+ <string name="choose_account_label" msgid="5557833752759831548">"Изаберите налог"</string>
+ <string name="add_account_label" msgid="4067610644298737417">"Додај налог"</string>
+ <string name="add_account_button_label" msgid="322390749416414097">"Додај налог"</string>
+ <string name="number_picker_increment_button" msgid="7621013714795186298">"Повећавање"</string>
+ <string name="number_picker_decrement_button" msgid="5116948444762708204">"Смањивање"</string>
+ <string name="number_picker_increment_scroll_mode" msgid="8403893549806805985">"<xliff:g id="VALUE">%s</xliff:g> додирните и задржите."</string>
+ <string name="number_picker_increment_scroll_action" msgid="8310191318914268271">"Превуците нагоре да бисте повећали, а надоле да бисте смањили."</string>
+ <string name="time_picker_increment_minute_button" msgid="7195870222945784300">"Повећавање минута"</string>
+ <string name="time_picker_decrement_minute_button" msgid="230925389943411490">"Смањивање минута"</string>
+ <string name="time_picker_increment_hour_button" msgid="3063572723197178242">"Повећавање сати"</string>
+ <string name="time_picker_decrement_hour_button" msgid="584101766855054412">"Смањивање сати"</string>
+ <string name="time_picker_increment_set_pm_button" msgid="5889149366900376419">"Подеси по подне"</string>
+ <string name="time_picker_decrement_set_am_button" msgid="1422608001541064087">"Подеси пре подне"</string>
+ <string name="date_picker_increment_month_button" msgid="3447263316096060309">"Повећавање месеца"</string>
+ <string name="date_picker_decrement_month_button" msgid="6531888937036883014">"Смањивање месеца"</string>
+ <string name="date_picker_increment_day_button" msgid="4349336637188534259">"Повећавање дана"</string>
+ <string name="date_picker_decrement_day_button" msgid="6840253837656637248">"Смањивање дана"</string>
+ <string name="date_picker_increment_year_button" msgid="7608128783435372594">"Повећавање године"</string>
+ <string name="date_picker_decrement_year_button" msgid="4102586521754172684">"Смањивање године"</string>
+ <string name="date_picker_prev_month_button" msgid="3418694374017868369">"Претходни месец"</string>
+ <string name="date_picker_next_month_button" msgid="4858207337779144840">"Следећи месец"</string>
<string name="keyboardview_keycode_alt" msgid="8997420058584292385">"Alt"</string>
- <string name="keyboardview_keycode_cancel" msgid="2134624484115716975">"Otkaži"</string>
- <string name="keyboardview_keycode_delete" msgid="2661117313730098650">"Izbriši"</string>
- <string name="keyboardview_keycode_done" msgid="2524518019001653851">"Gotovo"</string>
- <string name="keyboardview_keycode_mode_change" msgid="2743735349997999020">"Promena režima"</string>
+ <string name="keyboardview_keycode_cancel" msgid="2134624484115716975">"Откажи"</string>
+ <string name="keyboardview_keycode_delete" msgid="2661117313730098650">"Избриши"</string>
+ <string name="keyboardview_keycode_done" msgid="2524518019001653851">"Готово"</string>
+ <string name="keyboardview_keycode_mode_change" msgid="2743735349997999020">"Промена режима"</string>
<string name="keyboardview_keycode_shift" msgid="3026509237043975573">"Shift"</string>
<string name="keyboardview_keycode_enter" msgid="168054869339091055">"Enter"</string>
- <string name="activitychooserview_choose_application" msgid="3500574466367891463">"Izaberite aplikaciju"</string>
- <string name="activitychooserview_choose_application_error" msgid="6937782107559241734">"Nije moguće pokrenuti <xliff:g id="APPLICATION_NAME">%s</xliff:g>"</string>
- <string name="shareactionprovider_share_with" msgid="2753089758467748982">"Deli sa"</string>
- <string name="shareactionprovider_share_with_application" msgid="4902832247173666973">"Deli sa aplikacijom <xliff:g id="APPLICATION_NAME">%s</xliff:g>"</string>
- <string name="content_description_sliding_handle" msgid="982510275422590757">"Klizna ručica. Dodirnite i zadržite."</string>
- <string name="description_target_unlock_tablet" msgid="7431571180065859551">"Prevucite da biste otključali."</string>
- <string name="action_bar_home_description" msgid="1501655419158631974">"Kretanje do Početne"</string>
- <string name="action_bar_up_description" msgid="6611579697195026932">"Kretanje nagore"</string>
- <string name="action_menu_overflow_description" msgid="4579536843510088170">"Još opcija"</string>
+ <string name="activitychooserview_choose_application" msgid="3500574466367891463">"Изаберите апликацију"</string>
+ <string name="activitychooserview_choose_application_error" msgid="6937782107559241734">"Није могуће покренути <xliff:g id="APPLICATION_NAME">%s</xliff:g>"</string>
+ <string name="shareactionprovider_share_with" msgid="2753089758467748982">"Дели са"</string>
+ <string name="shareactionprovider_share_with_application" msgid="4902832247173666973">"Дели са апликацијом <xliff:g id="APPLICATION_NAME">%s</xliff:g>"</string>
+ <string name="content_description_sliding_handle" msgid="982510275422590757">"Клизна ручица. Додирните и задржите."</string>
+ <string name="description_target_unlock_tablet" msgid="7431571180065859551">"Превуците да бисте откључали."</string>
+ <string name="action_bar_home_description" msgid="1501655419158631974">"Кретање до Почетне"</string>
+ <string name="action_bar_up_description" msgid="6611579697195026932">"Кретање нагоре"</string>
+ <string name="action_menu_overflow_description" msgid="4579536843510088170">"Још опција"</string>
<string name="action_bar_home_description_format" msgid="5087107531331621803">"%1$s, %2$s"</string>
<string name="action_bar_home_subtitle_description_format" msgid="4346835454749569826">"%1$s, %2$s, %3$s"</string>
- <string name="storage_internal" msgid="8490227947584914460">"Unutrašnji deljeni memorijski prostor"</string>
- <string name="storage_sd_card" msgid="3404740277075331881">"SD kartica"</string>
- <string name="storage_sd_card_label" msgid="7526153141147470509">"<xliff:g id="MANUFACTURER">%s</xliff:g> SD kartica"</string>
- <string name="storage_usb_drive" msgid="448030813201444573">"USB disk"</string>
- <string name="storage_usb_drive_label" msgid="6631740655876540521">"<xliff:g id="MANUFACTURER">%s</xliff:g> USB disk"</string>
- <string name="storage_usb" msgid="2391213347883616886">"USB memorija"</string>
- <string name="extract_edit_menu_button" msgid="63954536535863040">"Izmeni"</string>
- <string name="data_usage_warning_title" msgid="9034893717078325845">"Upozorenje na potrošnju podataka"</string>
- <string name="data_usage_warning_body" msgid="1669325367188029454">"Potrošili ste <xliff:g id="APP">%s</xliff:g> podataka"</string>
- <string name="data_usage_mobile_limit_title" msgid="3911447354393775241">"Dostigli ste ograničenje podataka"</string>
- <string name="data_usage_wifi_limit_title" msgid="2069698056520812232">"Nema više WiFi podataka"</string>
- <string name="data_usage_limit_body" msgid="3567699582000085710">"Podaci su pauzirani tokom ostatka ciklusa"</string>
- <string name="data_usage_mobile_limit_snoozed_title" msgid="101888478915677895">"Potrošili ste mobilne podatke"</string>
- <string name="data_usage_wifi_limit_snoozed_title" msgid="1622359254521960508">"Potrošili ste WiFi podatke"</string>
- <string name="data_usage_limit_snoozed_body" msgid="545146591766765678">"Prekoračili ste <xliff:g id="SIZE">%s</xliff:g> od podešenog ograničenja"</string>
- <string name="data_usage_restricted_title" msgid="126711424380051268">"Pozadinski podaci su ograničeni"</string>
- <string name="data_usage_restricted_body" msgid="5338694433686077733">"Dodirnite za uklanjanje ograničenja."</string>
- <string name="data_usage_rapid_title" msgid="2950192123248740375">"Velika potrošnja mob. podataka"</string>
- <string name="data_usage_rapid_body" msgid="3886676853263693432">"Aplikacije su potrošile više podataka nego obično"</string>
- <string name="data_usage_rapid_app_body" msgid="5425779218506513861">"Aplikacija <xliff:g id="APP">%s</xliff:g> je potrošila više podataka nego obično"</string>
- <string name="ssl_certificate" msgid="5690020361307261997">"Bezbednosni sertifikat"</string>
- <string name="ssl_certificate_is_valid" msgid="7293675884598527081">"Ovaj sertifikat je važeći."</string>
- <string name="issued_to" msgid="5975877665505297662">"Izdato za:"</string>
- <string name="common_name" msgid="1486334593631798443">"Uobičajeni naziv:"</string>
- <string name="org_name" msgid="7526331696464255245">"Organizacija:"</string>
- <string name="org_unit" msgid="995934486977223076">"Organizaciona jedinica:"</string>
- <string name="issued_by" msgid="7872459822431585684">"Izdao/la:"</string>
- <string name="validity_period" msgid="1717724283033175968">"Važnost:"</string>
- <string name="issued_on" msgid="5855489688152497307">"Izdato:"</string>
- <string name="expires_on" msgid="1623640879705103121">"Ističe:"</string>
- <string name="serial_number" msgid="3479576915806623429">"Serijski broj:"</string>
- <string name="fingerprints" msgid="148690767172613723">"Digitalni otisci:"</string>
- <string name="sha256_fingerprint" msgid="7103976380961964600">"SHA-256 otisak prsta:"</string>
- <string name="sha1_fingerprint" msgid="2339915142825390774">"SHA-1 otisak prsta:"</string>
- <string name="activity_chooser_view_see_all" msgid="3917045206812726099">"Prikaži sve"</string>
- <string name="activity_chooser_view_dialog_title_default" msgid="8880731437191978314">"Izbor aktivnosti"</string>
- <string name="share_action_provider_share_with" msgid="1904096863622941880">"Deli sa"</string>
- <string name="sending" msgid="206925243621664438">"Slanje..."</string>
- <string name="launchBrowserDefault" msgid="6328349989932924119">"Želite li da pokrenete pregledač?"</string>
- <string name="SetupCallDefault" msgid="5581740063237175247">"Želite li da prihvatite poziv?"</string>
- <string name="activity_resolver_use_always" msgid="5575222334666843269">"Uvek"</string>
- <string name="activity_resolver_use_once" msgid="948462794469672658">"Samo jednom"</string>
- <string name="activity_resolver_work_profiles_support" msgid="4071345609235361269">"%1$s ne podržava poslovni profil"</string>
- <string name="default_audio_route_name" product="tablet" msgid="367936735632195517">"Tablet"</string>
- <string name="default_audio_route_name" product="tv" msgid="4908971385068087367">"TV"</string>
- <string name="default_audio_route_name" product="default" msgid="9213546147739983977">"Telefon"</string>
- <string name="default_audio_route_name_dock_speakers" msgid="1551166029093995289">"Zvučnici bazne stanice"</string>
- <string name="default_audio_route_name_external_device" msgid="8124229858618975">"Spoljni uređaj"</string>
- <string name="default_audio_route_name_headphones" msgid="6954070994792640762">"Slušalice"</string>
+ <string name="storage_internal" msgid="8490227947584914460">"Унутрашњи дељени меморијски простор"</string>
+ <string name="storage_sd_card" msgid="3404740277075331881">"SD картица"</string>
+ <string name="storage_sd_card_label" msgid="7526153141147470509">"<xliff:g id="MANUFACTURER">%s</xliff:g> SD картица"</string>
+ <string name="storage_usb_drive" msgid="448030813201444573">"USB диск"</string>
+ <string name="storage_usb_drive_label" msgid="6631740655876540521">"<xliff:g id="MANUFACTURER">%s</xliff:g> USB диск"</string>
+ <string name="storage_usb" msgid="2391213347883616886">"USB меморија"</string>
+ <string name="extract_edit_menu_button" msgid="63954536535863040">"Измени"</string>
+ <string name="data_usage_warning_title" msgid="9034893717078325845">"Упозорење на потрошњу података"</string>
+ <string name="data_usage_warning_body" msgid="1669325367188029454">"Потрошили сте <xliff:g id="APP">%s</xliff:g> података"</string>
+ <string name="data_usage_mobile_limit_title" msgid="3911447354393775241">"Достигли сте ограничење података"</string>
+ <string name="data_usage_wifi_limit_title" msgid="2069698056520812232">"Нема више WiFi података"</string>
+ <string name="data_usage_limit_body" msgid="3567699582000085710">"Подаци су паузирани током остатка циклуса"</string>
+ <string name="data_usage_mobile_limit_snoozed_title" msgid="101888478915677895">"Потрошили сте мобилне податке"</string>
+ <string name="data_usage_wifi_limit_snoozed_title" msgid="1622359254521960508">"Потрошили сте WiFi податке"</string>
+ <string name="data_usage_limit_snoozed_body" msgid="545146591766765678">"Прекорачили сте <xliff:g id="SIZE">%s</xliff:g> од подешеног ограничења"</string>
+ <string name="data_usage_restricted_title" msgid="126711424380051268">"Позадински подаци су ограничени"</string>
+ <string name="data_usage_restricted_body" msgid="5338694433686077733">"Додирните за уклањање ограничења."</string>
+ <string name="data_usage_rapid_title" msgid="2950192123248740375">"Велика потрошња моб. података"</string>
+ <string name="data_usage_rapid_body" msgid="3886676853263693432">"Апликације су потрошиле више података него обично"</string>
+ <string name="data_usage_rapid_app_body" msgid="5425779218506513861">"Апликација <xliff:g id="APP">%s</xliff:g> је потрошила више података него обично"</string>
+ <string name="ssl_certificate" msgid="5690020361307261997">"Безбедносни сертификат"</string>
+ <string name="ssl_certificate_is_valid" msgid="7293675884598527081">"Овај сертификат је важећи."</string>
+ <string name="issued_to" msgid="5975877665505297662">"Издато за:"</string>
+ <string name="common_name" msgid="1486334593631798443">"Уобичајени назив:"</string>
+ <string name="org_name" msgid="7526331696464255245">"Организација:"</string>
+ <string name="org_unit" msgid="995934486977223076">"Организациона јединица:"</string>
+ <string name="issued_by" msgid="7872459822431585684">"Издао/ла:"</string>
+ <string name="validity_period" msgid="1717724283033175968">"Важност:"</string>
+ <string name="issued_on" msgid="5855489688152497307">"Издато:"</string>
+ <string name="expires_on" msgid="1623640879705103121">"Истиче:"</string>
+ <string name="serial_number" msgid="3479576915806623429">"Серијски број:"</string>
+ <string name="fingerprints" msgid="148690767172613723">"Дигитални отисци:"</string>
+ <string name="sha256_fingerprint" msgid="7103976380961964600">"SHA-256 отисак прста:"</string>
+ <string name="sha1_fingerprint" msgid="2339915142825390774">"SHA-1 отисак прста:"</string>
+ <string name="activity_chooser_view_see_all" msgid="3917045206812726099">"Прикажи све"</string>
+ <string name="activity_chooser_view_dialog_title_default" msgid="8880731437191978314">"Избор активности"</string>
+ <string name="share_action_provider_share_with" msgid="1904096863622941880">"Дели са"</string>
+ <string name="sending" msgid="206925243621664438">"Слање..."</string>
+ <string name="launchBrowserDefault" msgid="6328349989932924119">"Желите ли да покренете прегледач?"</string>
+ <string name="SetupCallDefault" msgid="5581740063237175247">"Желите ли да прихватите позив?"</string>
+ <string name="activity_resolver_use_always" msgid="5575222334666843269">"Увек"</string>
+ <string name="activity_resolver_use_once" msgid="948462794469672658">"Само једном"</string>
+ <string name="activity_resolver_work_profiles_support" msgid="4071345609235361269">"%1$s не подржава пословни профил"</string>
+ <string name="default_audio_route_name" product="tablet" msgid="367936735632195517">"Таблет"</string>
+ <string name="default_audio_route_name" product="tv" msgid="4908971385068087367">"ТВ"</string>
+ <string name="default_audio_route_name" product="default" msgid="9213546147739983977">"Телефон"</string>
+ <string name="default_audio_route_name_dock_speakers" msgid="1551166029093995289">"Звучници базне станице"</string>
+ <string name="default_audio_route_name_external_device" msgid="8124229858618975">"Спољни уређај"</string>
+ <string name="default_audio_route_name_headphones" msgid="6954070994792640762">"Слушалице"</string>
<string name="default_audio_route_name_usb" msgid="895668743163316932">"USB"</string>
- <string name="default_audio_route_category_name" msgid="5241740395748134483">"Sistem"</string>
- <string name="bluetooth_a2dp_audio_route_name" msgid="4214648773120426288">"Bluetooth audio"</string>
- <string name="wireless_display_route_description" msgid="8297563323032966831">"Bežični ekran"</string>
- <string name="media_route_button_content_description" msgid="2299223698196869956">"Prebacuj"</string>
- <string name="media_route_chooser_title" msgid="6646594924991269208">"Povežite sa uređajem"</string>
- <string name="media_route_chooser_title_for_remote_display" msgid="3105906508794326446">"Prebacite ekran na uređaj"</string>
- <string name="media_route_chooser_searching" msgid="6119673534251329535">"Traženje uređaja…"</string>
- <string name="media_route_chooser_extended_settings" msgid="2506352159381327741">"Podešavanja"</string>
- <string name="media_route_controller_disconnect" msgid="7362617572732576959">"Prekini vezu"</string>
- <string name="media_route_status_scanning" msgid="8045156315309594482">"Skeniranje..."</string>
- <string name="media_route_status_connecting" msgid="5845597961412010540">"Povezuje se..."</string>
- <string name="media_route_status_available" msgid="1477537663492007608">"Dostupna"</string>
- <string name="media_route_status_not_available" msgid="480912417977515261">"Nisu dostupne"</string>
- <string name="media_route_status_in_use" msgid="6684112905244944724">"U upotrebi"</string>
- <string name="display_manager_built_in_display_name" msgid="1015775198829722440">"Ugrađeni ekran"</string>
- <string name="display_manager_hdmi_display_name" msgid="1022758026251534975">"HDMI ekran"</string>
- <string name="display_manager_overlay_display_name" msgid="5306088205181005861">"Postavljeni element br. <xliff:g id="ID">%1$d</xliff:g>"</string>
+ <string name="default_audio_route_category_name" msgid="5241740395748134483">"Систем"</string>
+ <string name="bluetooth_a2dp_audio_route_name" msgid="4214648773120426288">"Bluetooth аудио"</string>
+ <string name="wireless_display_route_description" msgid="8297563323032966831">"Бежични екран"</string>
+ <string name="media_route_button_content_description" msgid="2299223698196869956">"Пребацуј"</string>
+ <string name="media_route_chooser_title" msgid="6646594924991269208">"Повежите са уређајем"</string>
+ <string name="media_route_chooser_title_for_remote_display" msgid="3105906508794326446">"Пребаците екран на уређај"</string>
+ <string name="media_route_chooser_searching" msgid="6119673534251329535">"Тражење уређаја…"</string>
+ <string name="media_route_chooser_extended_settings" msgid="2506352159381327741">"Подешавања"</string>
+ <string name="media_route_controller_disconnect" msgid="7362617572732576959">"Прекини везу"</string>
+ <string name="media_route_status_scanning" msgid="8045156315309594482">"Скенирање..."</string>
+ <string name="media_route_status_connecting" msgid="5845597961412010540">"Повезује се..."</string>
+ <string name="media_route_status_available" msgid="1477537663492007608">"Доступна"</string>
+ <string name="media_route_status_not_available" msgid="480912417977515261">"Нису доступне"</string>
+ <string name="media_route_status_in_use" msgid="6684112905244944724">"У употреби"</string>
+ <string name="display_manager_built_in_display_name" msgid="1015775198829722440">"Уграђени екран"</string>
+ <string name="display_manager_hdmi_display_name" msgid="1022758026251534975">"HDMI екран"</string>
+ <string name="display_manager_overlay_display_name" msgid="5306088205181005861">"Постављени елемент бр. <xliff:g id="ID">%1$d</xliff:g>"</string>
<string name="display_manager_overlay_display_title" msgid="1480158037150469170">"<xliff:g id="NAME">%1$s</xliff:g>: <xliff:g id="WIDTH">%2$d</xliff:g>×<xliff:g id="HEIGHT">%3$d</xliff:g>, <xliff:g id="DPI">%4$d</xliff:g> dpi"</string>
- <string name="display_manager_overlay_display_secure_suffix" msgid="2810034719482834679">", bezbedno"</string>
- <string name="kg_forgot_pattern_button_text" msgid="406145459223122537">"Zaboravljeni šablon"</string>
- <string name="kg_wrong_pattern" msgid="1342812634464179931">"Pogrešan šablon"</string>
- <string name="kg_wrong_password" msgid="2384677900494439426">"Pogrešna lozinka"</string>
- <string name="kg_wrong_pin" msgid="3680925703673166482">"Pogrešan PIN"</string>
- <string name="kg_pattern_instructions" msgid="8366024510502517748">"Nacrtajte šablon"</string>
- <string name="kg_sim_pin_instructions" msgid="6479401489471690359">"Unesite PIN SIM kartice"</string>
- <string name="kg_pin_instructions" msgid="7355933174673539021">"Unesite PIN"</string>
- <string name="kg_password_instructions" msgid="7179782578809398050">"Unesite lozinku"</string>
- <string name="kg_puk_enter_puk_hint" msgid="6696187482616360994">"SIM kartica je sada onemogućena. Unesite PUK kôd da biste nastavili. Za detalje kontaktirajte operatera."</string>
- <string name="kg_puk_enter_pin_hint" msgid="8190982314659429770">"Unesite željeni PIN kôd"</string>
- <string name="kg_enter_confirm_pin_hint" msgid="6372557107414074580">"Potvrdite željeni PIN kôd"</string>
+ <string name="display_manager_overlay_display_secure_suffix" msgid="2810034719482834679">", безбедно"</string>
+ <string name="kg_forgot_pattern_button_text" msgid="406145459223122537">"Заборављени шаблон"</string>
+ <string name="kg_wrong_pattern" msgid="1342812634464179931">"Погрешан шаблон"</string>
+ <string name="kg_wrong_password" msgid="2384677900494439426">"Погрешна лозинка"</string>
+ <string name="kg_wrong_pin" msgid="3680925703673166482">"Погрешан PIN"</string>
+ <string name="kg_pattern_instructions" msgid="8366024510502517748">"Нацртајте шаблон"</string>
+ <string name="kg_sim_pin_instructions" msgid="6479401489471690359">"Унесите PIN SIM картице"</string>
+ <string name="kg_pin_instructions" msgid="7355933174673539021">"Унесите PIN"</string>
+ <string name="kg_password_instructions" msgid="7179782578809398050">"Унесите лозинку"</string>
+ <string name="kg_puk_enter_puk_hint" msgid="6696187482616360994">"SIM картица је сада онемогућена. Унесите PUK кôд да бисте наставили. За детаље контактирајте оператера."</string>
+ <string name="kg_puk_enter_pin_hint" msgid="8190982314659429770">"Унесите жељени PIN кôд"</string>
+ <string name="kg_enter_confirm_pin_hint" msgid="6372557107414074580">"Потврдите жељени PIN кôд"</string>
<!-- no translation found for kg_sim_unlock_progress_dialog_message (5743634657721110967) -->
<skip />
- <string name="kg_password_wrong_pin_code" msgid="9013856346870572451">"PIN kôd je netačan."</string>
- <string name="kg_invalid_sim_pin_hint" msgid="4821601451222564077">"Unesite PIN koji ima od 4 do 8 brojeva."</string>
- <string name="kg_invalid_sim_puk_hint" msgid="2539364558870734339">"PUK kôd treba da ima 8 brojeva."</string>
- <string name="kg_invalid_puk" msgid="4809502818518963344">"Ponovo unesite ispravni PUK kôd. Ponovljeni pokušaji će trajno onemogućiti SIM."</string>
- <string name="kg_invalid_confirm_pin_hint" product="default" msgid="4705368340409816254">"PIN kodovi se ne podudaraju"</string>
- <string name="kg_login_too_many_attempts" msgid="699292728290654121">"Previše pokušaja unosa šablona"</string>
- <string name="kg_login_instructions" msgid="3619844310339066827">"Da biste otključali, prijavite se pomoću Google naloga."</string>
- <string name="kg_login_username_hint" msgid="1765453775467133251">"Korisničko ime (imejl adresa)"</string>
- <string name="kg_login_password_hint" msgid="3330530727273164402">"Lozinka"</string>
- <string name="kg_login_submit_button" msgid="893611277617096870">"Prijavi me"</string>
- <string name="kg_login_invalid_input" msgid="8292367491901220210">"Nevažeće korisničko ime ili lozinka."</string>
- <string name="kg_login_account_recovery_hint" msgid="4892466171043541248">"Zaboravili ste korisničko ime ili lozinku?\nPosetite adresu "<b>"google.com/accounts/recovery"</b>"."</string>
- <string name="kg_login_checking_password" msgid="4676010303243317253">"Provera naloga…"</string>
- <string name="kg_too_many_failed_pin_attempts_dialog_message" msgid="23741434207544038">"Uneli ste netačni PIN <xliff:g id="NUMBER_0">%1$d</xliff:g> puta. \n\nProbajte ponovo za <xliff:g id="NUMBER_1">%2$d</xliff:g> sekunde/i."</string>
- <string name="kg_too_many_failed_password_attempts_dialog_message" msgid="3328686432962224215">"Uneli ste netačnu lozinku <xliff:g id="NUMBER_0">%1$d</xliff:g> puta. \n\nProbajte ponovo za <xliff:g id="NUMBER_1">%2$d</xliff:g> sekunde/i."</string>
- <string name="kg_too_many_failed_pattern_attempts_dialog_message" msgid="7357404233979139075">"Nacrtali ste šablon za otključavanje netačno <xliff:g id="NUMBER_0">%1$d</xliff:g> puta. \n\nProbajte ponovo za <xliff:g id="NUMBER_1">%2$d</xliff:g> sekunde/i."</string>
- <string name="kg_failed_attempts_almost_at_wipe" product="tablet" msgid="3479940221343361587">"Pokušali ste da otključate tablet netačno <xliff:g id="NUMBER_0">%1$d</xliff:g> puta. Nakon još <xliff:g id="NUMBER_1">%2$d</xliff:g> neuspešna(ih) pokušaja tablet će biti resetovan na fabrička podešavanja i svi korisnički podaci će biti izgubljeni."</string>
- <string name="kg_failed_attempts_almost_at_wipe" product="tv" msgid="9064457748587850217">"Broj vaših neuspešnih pokušaja da otključate Android TV uređaj: <xliff:g id="NUMBER_0">%1$d</xliff:g>. Broj preostalih neuspešnih pokušaja posle kojih će se Android TV resetovati na fabrička podešavanja i svi podaci korisnika će biti izgubljeni: <xliff:g id="NUMBER_1">%2$d</xliff:g>."</string>
- <string name="kg_failed_attempts_almost_at_wipe" product="default" msgid="5955398963754432548">"Pokušali ste da otključate telefon netačno <xliff:g id="NUMBER_0">%1$d</xliff:g> puta. Posle još <xliff:g id="NUMBER_1">%2$d</xliff:g> neuspešna(ih) pokušaja telefon će biti resetovan na fabrička podešavanja i svi korisnički podaci će biti izgubljeni."</string>
- <string name="kg_failed_attempts_now_wiping" product="tablet" msgid="2299099385175083308">"Pokušali ste da otključate tablet netačno <xliff:g id="NUMBER">%d</xliff:g> puta. Tablet će sada biti vraćen na podrazumevana fabrička podešavanja."</string>
- <string name="kg_failed_attempts_now_wiping" product="tv" msgid="5045460916106267585">"Broj vaših neuspešnih pokušaja da otključate Android TV uređaj: <xliff:g id="NUMBER">%d</xliff:g>. Android TV uređaj će se sada resetovati na fabrička podešavanja."</string>
- <string name="kg_failed_attempts_now_wiping" product="default" msgid="5043730590446071189">"Pokušali ste da otključate telefon netačno <xliff:g id="NUMBER">%d</xliff:g> puta. Telefon će sada biti vraćen na podrazumevana fabrička podešavanja."</string>
- <string name="kg_failed_attempts_almost_at_login" product="tablet" msgid="7086799295109717623">"Nacrtali ste šablon za otključavanje netačno <xliff:g id="NUMBER_0">%1$d</xliff:g> puta. Posle još <xliff:g id="NUMBER_1">%2$d</xliff:g> neuspešna(ih) pokušaja, od vas će biti zatraženo da otključate tablet pomoću naloga e-pošte.\n\nProbajte ponovo za <xliff:g id="NUMBER_2">%3$d</xliff:g> sekunde/i."</string>
- <string name="kg_failed_attempts_almost_at_login" product="tv" msgid="4670840383567106114">"Netačno ste nacrtali šablon za otključavanje <xliff:g id="NUMBER_0">%1$d</xliff:g> puta. Ako pogrešno pokušate još puta (<xliff:g id="NUMBER_1">%2$d</xliff:g>), zatražićemo da otključate telefon pomoću Android TV uređaja.\n\n Probajte ponovo za <xliff:g id="NUMBER_2">%3$d</xliff:g> sek."</string>
- <string name="kg_failed_attempts_almost_at_login" product="default" msgid="5270861875006378092">"Nacrtali ste šablon za otključavanje netačno <xliff:g id="NUMBER_0">%1$d</xliff:g> puta. Posle još <xliff:g id="NUMBER_1">%2$d</xliff:g> neuspešna(ih) pokušaja, od vas će biti zatraženo da otključate telefon pomoću naloga e-pošte.\n\nProbajte ponovo za <xliff:g id="NUMBER_2">%3$d</xliff:g> sekunde/i."</string>
+ <string name="kg_password_wrong_pin_code" msgid="9013856346870572451">"PIN кôд је нетачан."</string>
+ <string name="kg_invalid_sim_pin_hint" msgid="4821601451222564077">"Унесите PIN који има од 4 до 8 бројева."</string>
+ <string name="kg_invalid_sim_puk_hint" msgid="2539364558870734339">"PUK кôд треба да има 8 бројева."</string>
+ <string name="kg_invalid_puk" msgid="4809502818518963344">"Поново унесите исправни PUK кôд. Поновљени покушаји ће трајно онемогућити SIM."</string>
+ <string name="kg_invalid_confirm_pin_hint" product="default" msgid="4705368340409816254">"PIN кодови се не подударају"</string>
+ <string name="kg_login_too_many_attempts" msgid="699292728290654121">"Превише покушаја уноса шаблона"</string>
+ <string name="kg_login_instructions" msgid="3619844310339066827">"Да бисте откључали, пријавите се помоћу Google налога."</string>
+ <string name="kg_login_username_hint" msgid="1765453775467133251">"Корисничко име (имејл адреса)"</string>
+ <string name="kg_login_password_hint" msgid="3330530727273164402">"Лозинка"</string>
+ <string name="kg_login_submit_button" msgid="893611277617096870">"Пријави ме"</string>
+ <string name="kg_login_invalid_input" msgid="8292367491901220210">"Неважеће корисничко име или лозинка."</string>
+ <string name="kg_login_account_recovery_hint" msgid="4892466171043541248">"Заборавили сте корисничко име или лозинку?\nПосетите адресу "<b>"google.com/accounts/recovery"</b>"."</string>
+ <string name="kg_login_checking_password" msgid="4676010303243317253">"Провера налога…"</string>
+ <string name="kg_too_many_failed_pin_attempts_dialog_message" msgid="23741434207544038">"Унели сте нетачни PIN <xliff:g id="NUMBER_0">%1$d</xliff:g> пута. \n\nПробајте поново за <xliff:g id="NUMBER_1">%2$d</xliff:g> секунде/и."</string>
+ <string name="kg_too_many_failed_password_attempts_dialog_message" msgid="3328686432962224215">"Унели сте нетачну лозинку <xliff:g id="NUMBER_0">%1$d</xliff:g> пута. \n\nПробајте поново за <xliff:g id="NUMBER_1">%2$d</xliff:g> секунде/и."</string>
+ <string name="kg_too_many_failed_pattern_attempts_dialog_message" msgid="7357404233979139075">"Нацртали сте шаблон за откључавање нетачно <xliff:g id="NUMBER_0">%1$d</xliff:g> пута. \n\nПробајте поново за <xliff:g id="NUMBER_1">%2$d</xliff:g> секунде/и."</string>
+ <string name="kg_failed_attempts_almost_at_wipe" product="tablet" msgid="3479940221343361587">"Покушали сте да откључате таблет нетачно <xliff:g id="NUMBER_0">%1$d</xliff:g> пута. Након још <xliff:g id="NUMBER_1">%2$d</xliff:g> неуспешна(их) покушаја таблет ће бити ресетован на фабричка подешавања и сви кориснички подаци ће бити изгубљени."</string>
+ <string name="kg_failed_attempts_almost_at_wipe" product="tv" msgid="9064457748587850217">"Број ваших неуспешних покушаја да откључате Android TV уређај: <xliff:g id="NUMBER_0">%1$d</xliff:g>. Број преосталих неуспешних покушаја после којих ће се Android TV ресетовати на фабричка подешавања и сви подаци корисника ће бити изгубљени: <xliff:g id="NUMBER_1">%2$d</xliff:g>."</string>
+ <string name="kg_failed_attempts_almost_at_wipe" product="default" msgid="5955398963754432548">"Покушали сте да откључате телефон нетачно <xliff:g id="NUMBER_0">%1$d</xliff:g> пута. После још <xliff:g id="NUMBER_1">%2$d</xliff:g> неуспешна(их) покушаја телефон ће бити ресетован на фабричка подешавања и сви кориснички подаци ће бити изгубљени."</string>
+ <string name="kg_failed_attempts_now_wiping" product="tablet" msgid="2299099385175083308">"Покушали сте да откључате таблет нетачно <xliff:g id="NUMBER">%d</xliff:g> пута. Таблет ће сада бити враћен на подразумевана фабричка подешавања."</string>
+ <string name="kg_failed_attempts_now_wiping" product="tv" msgid="5045460916106267585">"Број ваших неуспешних покушаја да откључате Android TV уређај: <xliff:g id="NUMBER">%d</xliff:g>. Android TV уређај ће се сада ресетовати на фабричка подешавања."</string>
+ <string name="kg_failed_attempts_now_wiping" product="default" msgid="5043730590446071189">"Покушали сте да откључате телефон нетачно <xliff:g id="NUMBER">%d</xliff:g> пута. Телефон ће сада бити враћен на подразумевана фабричка подешавања."</string>
+ <string name="kg_failed_attempts_almost_at_login" product="tablet" msgid="7086799295109717623">"Нацртали сте шаблон за откључавање нетачно <xliff:g id="NUMBER_0">%1$d</xliff:g> пута. После још <xliff:g id="NUMBER_1">%2$d</xliff:g> неуспешна(их) покушаја, од вас ће бити затражено да откључате таблет помоћу налога е-поште.\n\nПробајте поново за <xliff:g id="NUMBER_2">%3$d</xliff:g> секунде/и."</string>
+ <string name="kg_failed_attempts_almost_at_login" product="tv" msgid="4670840383567106114">"Нетачно сте нацртали шаблон за откључавање <xliff:g id="NUMBER_0">%1$d</xliff:g> пута. Ако погрешно покушате још пута (<xliff:g id="NUMBER_1">%2$d</xliff:g>), затражићемо да откључате телефон помоћу Android TV уређаја.\n\n Пробајте поново за <xliff:g id="NUMBER_2">%3$d</xliff:g> сек."</string>
+ <string name="kg_failed_attempts_almost_at_login" product="default" msgid="5270861875006378092">"Нацртали сте шаблон за откључавање нетачно <xliff:g id="NUMBER_0">%1$d</xliff:g> пута. После још <xliff:g id="NUMBER_1">%2$d</xliff:g> неуспешна(их) покушаја, од вас ће бити затражено да откључате телефон помоћу налога е-поште.\n\nПробајте поново за <xliff:g id="NUMBER_2">%3$d</xliff:g> секунде/и."</string>
<string name="kg_text_message_separator" product="default" msgid="4503708889934976866">" – "</string>
- <string name="kg_reordering_delete_drop_target_text" msgid="2034358143731750914">"Ukloni"</string>
- <string name="safe_media_volume_warning" product="default" msgid="3751676824423049994">"Želite da pojačate zvuk iznad preporučenog nivoa?\n\nSlušanje glasne muzike duže vreme može da vam ošteti sluh."</string>
- <string name="accessibility_shortcut_warning_dialog_title" msgid="4017995837692622933">"Želite li da koristite prečicu za pristupačnost?"</string>
- <string name="accessibility_shortcut_toogle_warning" msgid="4161716521310929544">"Kada je prečica uključena, pritisnite oba dugmeta za jačinu zvuka da biste pokrenuli funkciju pristupačnosti."</string>
- <string name="accessibility_shortcut_multiple_service_warning_title" msgid="3135860819356676426">"Želite da uključite prečicu za funkcije pristupačnosti?"</string>
- <string name="accessibility_shortcut_multiple_service_warning" msgid="3740723309483706911">"Ako zadržite oba tastera za jačinu zvuka par sekundi, uključiće se funkcije pristupačnosti. To može da promeni način rada uređaja.\n\nPostojeće funkcije:\n<xliff:g id="SERVICE">%1$s</xliff:g>\nMožete da promenite izabrane funkcije u odeljku Podešavanja > Pristupačnost."</string>
+ <string name="kg_reordering_delete_drop_target_text" msgid="2034358143731750914">"Уклони"</string>
+ <string name="safe_media_volume_warning" product="default" msgid="3751676824423049994">"Желите да појачате звук изнад препорученог нивоа?\n\nСлушање гласне музике дуже време може да вам оштети слух."</string>
+ <string name="accessibility_shortcut_warning_dialog_title" msgid="4017995837692622933">"Желите ли да користите пречицу за приступачност?"</string>
+ <string name="accessibility_shortcut_toogle_warning" msgid="4161716521310929544">"Када је пречица укључена, притисните оба дугмета за јачину звука да бисте покренули функцију приступачности."</string>
+ <string name="accessibility_shortcut_multiple_service_warning_title" msgid="3135860819356676426">"Желите да укључите пречицу за функције приступачности?"</string>
+ <string name="accessibility_shortcut_multiple_service_warning" msgid="3740723309483706911">"Ако задржите оба тастера за јачину звука пар секунди, укључиће се функције приступачности. То може да промени начин рада уређаја.\n\nПостојеће функције:\n<xliff:g id="SERVICE">%1$s</xliff:g>\nМожете да промените изабране функције у одељку Подешавања > Приступачност."</string>
<string name="accessibility_shortcut_multiple_service_list" msgid="2128323171922023762">" • <xliff:g id="SERVICE">%1$s</xliff:g>\n"</string>
- <string name="accessibility_shortcut_single_service_warning_title" msgid="1909518473488345266">"Želite da uključite prečicu za uslugu <xliff:g id="SERVICE">%1$s</xliff:g>?"</string>
- <string name="accessibility_shortcut_single_service_warning" msgid="6363127705112844257">"Ako zadržite oba tastera za jačinu zvuka par sekundi, uključuje se <xliff:g id="SERVICE">%1$s</xliff:g>, funkcija pristupačnosti. To može da promeni način rada uređaja.\n\nMožete da promenite funkciju na koju se odnosi ova prečica u odeljku Podešavanja > Pristupačnost."</string>
- <string name="accessibility_shortcut_on" msgid="5463618449556111344">"Uključi"</string>
- <string name="accessibility_shortcut_off" msgid="3651336255403648739">"Ne uključuj"</string>
- <string name="accessibility_shortcut_menu_item_status_on" msgid="6608392117189732543">"UKLJUČENO"</string>
- <string name="accessibility_shortcut_menu_item_status_off" msgid="5531598275559472393">"ISKLJUČENO"</string>
- <string name="accessibility_enable_service_title" msgid="3931558336268541484">"Želite li da dozvolite da usluga <xliff:g id="SERVICE">%1$s</xliff:g> ima potpunu kontrolu nad uređajem?"</string>
- <string name="accessibility_service_warning_description" msgid="291674995220940133">"Potpuna kontrola je primerena za aplikacije koje vam pomažu kod usluga pristupačnosti, ali ne i za većinu aplikacija."</string>
- <string name="accessibility_service_screen_control_title" msgid="190017412626919776">"Pregledaj i kontroliši ekran"</string>
- <string name="accessibility_service_screen_control_description" msgid="6946315917771791525">"Može da čita sav sadržaj na ekranu i prikazuje ga u drugim aplikacijama."</string>
- <string name="accessibility_service_action_perform_title" msgid="779670378951658160">"Pregledaj i obavljaj radnje"</string>
- <string name="accessibility_service_action_perform_description" msgid="2718852014003170558">"Može da prati interakcije sa aplikacijom ili senzorom hardvera i koristi aplikacije umesto vas."</string>
- <string name="accessibility_dialog_button_allow" msgid="2092558122987144530">"Dozvoli"</string>
- <string name="accessibility_dialog_button_deny" msgid="4129575637812472671">"Odbij"</string>
- <string name="accessibility_select_shortcut_menu_title" msgid="6002726538854613272">"Dodirnite neku funkciju da biste počeli da je koristite:"</string>
- <string name="accessibility_edit_shortcut_menu_button_title" msgid="239446795930436325">"Odaberite funkcije koje ćete koristiti sa dugmetom Pristupačnost"</string>
- <string name="accessibility_edit_shortcut_menu_volume_title" msgid="1077294237378645981">"Odaberite funkcije za prečicu tasterom jačine zvuka"</string>
- <string name="accessibility_uncheck_legacy_item_warning" msgid="8047830891064817447">"Usluga <xliff:g id="SERVICE_NAME">%s</xliff:g> je isključena"</string>
- <string name="edit_accessibility_shortcut_menu_button" msgid="8885752738733772935">"Izmenite prečice"</string>
- <string name="done_accessibility_shortcut_menu_button" msgid="3668407723770815708">"Gotovo"</string>
- <string name="disable_accessibility_shortcut" msgid="5806091378745232383">"Isključi prečicu"</string>
- <string name="leave_accessibility_shortcut_on" msgid="6543362062336990814">"Koristi prečicu"</string>
- <string name="color_inversion_feature_name" msgid="326050048927789012">"Inverzija boja"</string>
+ <string name="accessibility_shortcut_single_service_warning_title" msgid="1909518473488345266">"Желите да укључите пречицу за услугу <xliff:g id="SERVICE">%1$s</xliff:g>?"</string>
+ <string name="accessibility_shortcut_single_service_warning" msgid="6363127705112844257">"Ако задржите оба тастера за јачину звука пар секунди, укључује се <xliff:g id="SERVICE">%1$s</xliff:g>, функција приступачности. То може да промени начин рада уређаја.\n\nМожете да промените функцију на коју се односи ова пречица у одељку Подешавања > Приступачност."</string>
+ <string name="accessibility_shortcut_on" msgid="5463618449556111344">"Укључи"</string>
+ <string name="accessibility_shortcut_off" msgid="3651336255403648739">"Не укључуј"</string>
+ <string name="accessibility_shortcut_menu_item_status_on" msgid="6608392117189732543">"УКЉУЧЕНО"</string>
+ <string name="accessibility_shortcut_menu_item_status_off" msgid="5531598275559472393">"ИСКЉУЧЕНО"</string>
+ <string name="accessibility_enable_service_title" msgid="3931558336268541484">"Желите ли да дозволите да услуга <xliff:g id="SERVICE">%1$s</xliff:g> има потпуну контролу над уређајем?"</string>
+ <string name="accessibility_service_warning_description" msgid="291674995220940133">"Потпуна контрола је примерена за апликације које вам помажу код услуга приступачности, али не и за већину апликација."</string>
+ <string name="accessibility_service_screen_control_title" msgid="190017412626919776">"Прегледај и контролиши екран"</string>
+ <string name="accessibility_service_screen_control_description" msgid="6946315917771791525">"Може да чита сав садржај на екрану и приказује га у другим апликацијама."</string>
+ <string name="accessibility_service_action_perform_title" msgid="779670378951658160">"Прегледај и обављај радње"</string>
+ <string name="accessibility_service_action_perform_description" msgid="2718852014003170558">"Може да прати интеракције са апликацијом или сензором хардвера и користи апликације уместо вас."</string>
+ <string name="accessibility_dialog_button_allow" msgid="2092558122987144530">"Дозволи"</string>
+ <string name="accessibility_dialog_button_deny" msgid="4129575637812472671">"Одбиј"</string>
+ <string name="accessibility_select_shortcut_menu_title" msgid="6002726538854613272">"Додирните неку функцију да бисте почели да је користите:"</string>
+ <string name="accessibility_edit_shortcut_menu_button_title" msgid="239446795930436325">"Одаберите функције које ћете користити са дугметом Приступачност"</string>
+ <string name="accessibility_edit_shortcut_menu_volume_title" msgid="1077294237378645981">"Одаберите функције за пречицу тастером јачине звука"</string>
+ <string name="accessibility_uncheck_legacy_item_warning" msgid="8047830891064817447">"Услуга <xliff:g id="SERVICE_NAME">%s</xliff:g> је искључена"</string>
+ <string name="edit_accessibility_shortcut_menu_button" msgid="8885752738733772935">"Измените пречице"</string>
+ <string name="done_accessibility_shortcut_menu_button" msgid="3668407723770815708">"Готово"</string>
+ <string name="disable_accessibility_shortcut" msgid="5806091378745232383">"Искључи пречицу"</string>
+ <string name="leave_accessibility_shortcut_on" msgid="6543362062336990814">"Користи пречицу"</string>
+ <string name="color_inversion_feature_name" msgid="326050048927789012">"Инверзија боја"</string>
<!-- no translation found for color_correction_feature_name (7975133554160979214) -->
<skip />
- <string name="one_handed_mode_feature_name" msgid="2334330034828094891">"Režim jednom rukom"</string>
- <string name="reduce_bright_colors_feature_name" msgid="3222994553174604132">"Dodatno zatamnjeno"</string>
- <string name="accessibility_shortcut_enabling_service" msgid="5473495203759847687">"Držali ste tastere za jačinu zvuka. Usluga <xliff:g id="SERVICE_NAME">%1$s</xliff:g> je uključena."</string>
- <string name="accessibility_shortcut_disabling_service" msgid="8675244165062700619">"Držali ste tastere za jačinu zvuka. Usluga <xliff:g id="SERVICE_NAME">%1$s</xliff:g> je isključena."</string>
- <string name="accessibility_shortcut_spoken_feedback" msgid="4228997042855695090">"Pritisnite i zadržite oba tastera za jačinu zvuka tri sekunde da biste koristili <xliff:g id="SERVICE_NAME">%1$s</xliff:g>"</string>
- <string name="accessibility_button_prompt_text" msgid="8343213623338605305">"Izaberite funkciju koja će se koristiti kada dodirnete dugme Pristupačnost:"</string>
- <string name="accessibility_gesture_prompt_text" msgid="8742535972130563952">"Odaberite funkciju koja će se koristiti pomoću pokreta za pristupačnost (pomoću dva prsta prevucite nagore od dna ekrana):"</string>
- <string name="accessibility_gesture_3finger_prompt_text" msgid="5211827854510660203">"Odaberite funkciju koja će se koristiti pomoću pokreta za pristupačnost (pomoću tri prsta prevucite nagore od dna ekrana):"</string>
- <string name="accessibility_button_instructional_text" msgid="8853928358872550500">"Da biste prelazili sa jedne funkcije na drugu, dodirnite i zadržite dugme Pristupačnost."</string>
- <string name="accessibility_gesture_instructional_text" msgid="9196230728837090497">"Da biste prelazili sa jedne funkcije na drugu, prevucite nagore pomoću dva prsta i zadržite."</string>
- <string name="accessibility_gesture_3finger_instructional_text" msgid="3425123684990193765">"Da biste prelazili sa jedne funkcije na drugu, prevucite nagore pomoću tri prsta i zadržite."</string>
- <string name="accessibility_magnification_chooser_text" msgid="1502075582164931596">"Uvećanje"</string>
- <string name="user_switched" msgid="7249833311585228097">"Aktuelni korisnik <xliff:g id="NAME">%1$s</xliff:g>."</string>
- <string name="user_switching_message" msgid="1912993630661332336">"Prebacivanje na <xliff:g id="NAME">%1$s</xliff:g>…"</string>
- <string name="user_logging_out_message" msgid="7216437629179710359">"Odjavljuje se <xliff:g id="NAME">%1$s</xliff:g>…"</string>
- <string name="owner_name" msgid="8713560351570795743">"Vlasnik"</string>
- <string name="guest_name" msgid="8502103277839834324">"Gost"</string>
- <string name="error_message_title" msgid="4082495589294631966">"Greška"</string>
- <string name="error_message_change_not_allowed" msgid="843159705042381454">"Administrator nije dozvolio ovu promenu"</string>
- <string name="app_not_found" msgid="3429506115332341800">"Nije pronađena nijedna aplikacija koja bi mogla da obavi ovu radnju"</string>
- <string name="revoke" msgid="5526857743819590458">"Opozovi"</string>
+ <string name="one_handed_mode_feature_name" msgid="2334330034828094891">"Режим једном руком"</string>
+ <string name="reduce_bright_colors_feature_name" msgid="3222994553174604132">"Додатно затамњено"</string>
+ <string name="accessibility_shortcut_enabling_service" msgid="5473495203759847687">"Држали сте тастере за јачину звука. Услуга <xliff:g id="SERVICE_NAME">%1$s</xliff:g> је укључена."</string>
+ <string name="accessibility_shortcut_disabling_service" msgid="8675244165062700619">"Држали сте тастере за јачину звука. Услуга <xliff:g id="SERVICE_NAME">%1$s</xliff:g> је искључена."</string>
+ <string name="accessibility_shortcut_spoken_feedback" msgid="4228997042855695090">"Притисните и задржите оба тастера за јачину звука три секунде да бисте користили <xliff:g id="SERVICE_NAME">%1$s</xliff:g>"</string>
+ <string name="accessibility_button_prompt_text" msgid="8343213623338605305">"Изаберите функцију која ће се користити када додирнете дугме Приступачност:"</string>
+ <string name="accessibility_gesture_prompt_text" msgid="8742535972130563952">"Одаберите функцију која ће се користити помоћу покрета за приступачност (помоћу два прста превуците нагоре од дна екрана):"</string>
+ <string name="accessibility_gesture_3finger_prompt_text" msgid="5211827854510660203">"Одаберите функцију која ће се користити помоћу покрета за приступачност (помоћу три прста превуците нагоре од дна екрана):"</string>
+ <string name="accessibility_button_instructional_text" msgid="8853928358872550500">"Да бисте прелазили са једне функције на другу, додирните и задржите дугме Приступачност."</string>
+ <string name="accessibility_gesture_instructional_text" msgid="9196230728837090497">"Да бисте прелазили са једне функције на другу, превуците нагоре помоћу два прста и задржите."</string>
+ <string name="accessibility_gesture_3finger_instructional_text" msgid="3425123684990193765">"Да бисте прелазили са једне функције на другу, превуците нагоре помоћу три прста и задржите."</string>
+ <string name="accessibility_magnification_chooser_text" msgid="1502075582164931596">"Увећање"</string>
+ <string name="user_switched" msgid="7249833311585228097">"Актуелни корисник <xliff:g id="NAME">%1$s</xliff:g>."</string>
+ <string name="user_switching_message" msgid="1912993630661332336">"Пребацивање на <xliff:g id="NAME">%1$s</xliff:g>…"</string>
+ <string name="user_logging_out_message" msgid="7216437629179710359">"Одјављује се <xliff:g id="NAME">%1$s</xliff:g>…"</string>
+ <string name="owner_name" msgid="8713560351570795743">"Власник"</string>
+ <string name="guest_name" msgid="8502103277839834324">"Гост"</string>
+ <string name="error_message_title" msgid="4082495589294631966">"Грешка"</string>
+ <string name="error_message_change_not_allowed" msgid="843159705042381454">"Администратор није дозволио ову промену"</string>
+ <string name="app_not_found" msgid="3429506115332341800">"Није пронађена ниједна апликација која би могла да обави ову радњу"</string>
+ <string name="revoke" msgid="5526857743819590458">"Опозови"</string>
<string name="mediasize_iso_a0" msgid="7039061159929977973">"ISO A0"</string>
<string name="mediasize_iso_a1" msgid="4063589931031977223">"ISO A1"</string>
<string name="mediasize_iso_a2" msgid="2779860175680233980">"ISO A2"</string>
@@ -1871,486 +1871,490 @@
<string name="mediasize_japanese_kaku2" msgid="7477551750461028312">"Kaku2"</string>
<string name="mediasize_japanese_you4" msgid="5552111912684384833">"You4"</string>
<string name="mediasize_japanese_l" msgid="1326765321473431817">"L"</string>
- <string name="mediasize_unknown_portrait" msgid="3817016220446495613">"Nepoznata veličina, uspravno"</string>
- <string name="mediasize_unknown_landscape" msgid="1584741567225095325">"Nepoznata veličina, vodoravno"</string>
- <string name="write_fail_reason_cancelled" msgid="2344081488493969190">"Otkazano je"</string>
- <string name="write_fail_reason_cannot_write" msgid="432118118378451508">"Greška pri ispisivanju sadržaja"</string>
- <string name="reason_unknown" msgid="5599739807581133337">"nepoznato"</string>
- <string name="reason_service_unavailable" msgid="5288405248063804713">"Usluga štampanja nije omogućena"</string>
- <string name="print_service_installed_title" msgid="6134880817336942482">"Usluga <xliff:g id="NAME">%s</xliff:g> je instalirana"</string>
- <string name="print_service_installed_message" msgid="7005672469916968131">"Dodirnite da biste omogućili"</string>
- <string name="restr_pin_enter_admin_pin" msgid="1199419462726962697">"Unesite PIN administratora"</string>
- <string name="restr_pin_enter_pin" msgid="373139384161304555">"Unesite PIN"</string>
- <string name="restr_pin_incorrect" msgid="3861383632940852496">"Netačno"</string>
- <string name="restr_pin_enter_old_pin" msgid="7537079094090650967">"Aktuelni PIN"</string>
- <string name="restr_pin_enter_new_pin" msgid="3267614461844565431">"Novi PIN"</string>
- <string name="restr_pin_confirm_pin" msgid="7143161971614944989">"Potvrdite novi PIN"</string>
- <string name="restr_pin_create_pin" msgid="917067613896366033">"Napravite PIN za izmenu ograničenja"</string>
- <string name="restr_pin_error_doesnt_match" msgid="7063392698489280556">"PIN-ovi se ne podudaraju. Probajte ponovo."</string>
- <string name="restr_pin_error_too_short" msgid="1547007808237941065">"PIN je prekratak. Mora da sadrži najmanje 4 cifre."</string>
- <string name="restr_pin_try_later" msgid="5897719962541636727">"Probajte ponovo kasnije"</string>
- <string name="immersive_cling_title" msgid="2307034298721541791">"Prikazuje se ceo ekran"</string>
- <string name="immersive_cling_description" msgid="7092737175345204832">"Da biste izašli, prevucite nadole odozgo."</string>
- <string name="immersive_cling_positive" msgid="7047498036346489883">"Važi"</string>
- <string name="done_label" msgid="7283767013231718521">"Gotovo"</string>
- <string name="hour_picker_description" msgid="5153757582093524635">"Kružni klizač za sate"</string>
- <string name="minute_picker_description" msgid="9029797023621927294">"Kružni klizač za minute"</string>
- <string name="select_hours" msgid="5982889657313147347">"Izaberite sate"</string>
- <string name="select_minutes" msgid="9157401137441014032">"Izaberite minute"</string>
- <string name="select_day" msgid="2060371240117403147">"Izaberite mesec i dan"</string>
- <string name="select_year" msgid="1868350712095595393">"Izaberite godinu"</string>
- <string name="deleted_key" msgid="9130083334943364001">"Izbrisali ste <xliff:g id="KEY">%1$s</xliff:g>"</string>
- <string name="managed_profile_label_badge" msgid="6762559569999499495">"<xliff:g id="LABEL">%1$s</xliff:g> na poslu"</string>
- <string name="managed_profile_label_badge_2" msgid="5673187309555352550">"2. poslovni <xliff:g id="LABEL">%1$s</xliff:g>"</string>
- <string name="managed_profile_label_badge_3" msgid="6882151970556391957">"3. poslovni imejl <xliff:g id="LABEL">%1$s</xliff:g>"</string>
- <string name="lock_to_app_unlock_pin" msgid="3890940811866290782">"Traži PIN pre otkačinjanja"</string>
- <string name="lock_to_app_unlock_pattern" msgid="2694204070499712503">"Traži šablon za otključavanje pre otkačinjanja"</string>
- <string name="lock_to_app_unlock_password" msgid="9126722403506560473">"Traži lozinku pre otkačinjanja"</string>
- <string name="package_installed_device_owner" msgid="7035926868974878525">"Instalirao je administrator"</string>
- <string name="package_updated_device_owner" msgid="7560272363805506941">"Ažurirao je administrator"</string>
- <string name="package_deleted_device_owner" msgid="2292335928930293023">"Izbrisao je administrator"</string>
- <string name="confirm_battery_saver" msgid="5247976246208245754">"Potvrdi"</string>
- <string name="battery_saver_description_with_learn_more" msgid="5444908404021316250">"Ušteda baterije uključuje tamnu temu i ograničava ili isključuje aktivnosti u pozadini, neke vizuelne efekte, određene funkcije i neke mrežne veze."</string>
- <string name="battery_saver_description" msgid="8518809702138617167">"Ušteda baterije uključuje tamnu temu i ograničava ili isključuje aktivnosti u pozadini, neke vizuelne efekte, određene funkcije i neke mrežne veze."</string>
- <string name="data_saver_description" msgid="4995164271550590517">"Da bi se smanjila potrošnja podataka, Ušteda podataka sprečava neke aplikacije da šalju ili primaju podatke u pozadini. Aplikacija koju trenutno koristite može da pristupa podacima, ali će to činiti ređe. Na primer, slike se neće prikazivati dok ih ne dodirnete."</string>
- <string name="data_saver_enable_title" msgid="7080620065745260137">"Želite da uključite Uštedu podataka?"</string>
- <string name="data_saver_enable_button" msgid="4399405762586419726">"Uključi"</string>
- <string name="zen_mode_duration_minutes_summary" msgid="4555514757230849789">"{count,plural, =1{Jedan minut (do {formattedTime})}one{# minut (do {formattedTime})}few{# minuta (do {formattedTime})}other{# minuta (do {formattedTime})}}"</string>
- <string name="zen_mode_duration_minutes_summary_short" msgid="1187553788355486950">"{count,plural, =1{1 min (do {formattedTime})}one{# min (do {formattedTime})}few{# min (do {formattedTime})}other{# min (do {formattedTime})}}"</string>
- <string name="zen_mode_duration_hours_summary" msgid="3866333100793277211">"{count,plural, =1{1 sat (do {formattedTime})}one{# sat (do {formattedTime})}few{# sata (do {formattedTime})}other{# sati (do {formattedTime})}}"</string>
- <string name="zen_mode_duration_hours_summary_short" msgid="687919813833347945">"{count,plural, =1{1 s (do {formattedTime})}one{# s (do {formattedTime})}few{# s (do {formattedTime})}other{# s (do {formattedTime})}}"</string>
- <string name="zen_mode_duration_minutes" msgid="2340007982276569054">"{count,plural, =1{Jedan minut}one{# minut}few{# minuta}other{# minuta}}"</string>
- <string name="zen_mode_duration_minutes_short" msgid="2435756450204526554">"{count,plural, =1{1 min}one{# min}few{# min}other{# min}}"</string>
- <string name="zen_mode_duration_hours" msgid="7841806065034711849">"{count,plural, =1{1 sat}one{# sat}few{# sata}other{# sati}}"</string>
- <string name="zen_mode_duration_hours_short" msgid="3666949653933099065">"{count,plural, =1{1 s}one{# s}few{# s}other{# s}}"</string>
- <string name="zen_mode_until_next_day" msgid="1403042784161725038">"Do <xliff:g id="FORMATTEDTIME">%1$s</xliff:g>"</string>
- <string name="zen_mode_until" msgid="2250286190237669079">"Do <xliff:g id="FORMATTEDTIME">%1$s</xliff:g>"</string>
- <string name="zen_mode_alarm" msgid="7046911727540499275">"Do <xliff:g id="FORMATTEDTIME">%1$s</xliff:g> (sledeći alarm)"</string>
- <string name="zen_mode_forever" msgid="740585666364912448">"Dok ne isključite"</string>
- <string name="zen_mode_forever_dnd" msgid="3423201955704180067">"Dok ne isključite režim Ne uznemiravaj"</string>
+ <string name="mediasize_unknown_portrait" msgid="3817016220446495613">"Непозната величина, усправно"</string>
+ <string name="mediasize_unknown_landscape" msgid="1584741567225095325">"Непозната величина, водоравно"</string>
+ <string name="write_fail_reason_cancelled" msgid="2344081488493969190">"Отказано је"</string>
+ <string name="write_fail_reason_cannot_write" msgid="432118118378451508">"Грешка при исписивању садржаја"</string>
+ <string name="reason_unknown" msgid="5599739807581133337">"непознато"</string>
+ <string name="reason_service_unavailable" msgid="5288405248063804713">"Услуга штампања није омогућена"</string>
+ <string name="print_service_installed_title" msgid="6134880817336942482">"Услуга <xliff:g id="NAME">%s</xliff:g> је инсталирана"</string>
+ <string name="print_service_installed_message" msgid="7005672469916968131">"Додирните да бисте омогућили"</string>
+ <string name="restr_pin_enter_admin_pin" msgid="1199419462726962697">"Унесите PIN администратора"</string>
+ <string name="restr_pin_enter_pin" msgid="373139384161304555">"Унесите PIN"</string>
+ <string name="restr_pin_incorrect" msgid="3861383632940852496">"Нетачно"</string>
+ <string name="restr_pin_enter_old_pin" msgid="7537079094090650967">"Актуелни PIN"</string>
+ <string name="restr_pin_enter_new_pin" msgid="3267614461844565431">"Нови PIN"</string>
+ <string name="restr_pin_confirm_pin" msgid="7143161971614944989">"Потврдите нови PIN"</string>
+ <string name="restr_pin_create_pin" msgid="917067613896366033">"Направите PIN за измену ограничења"</string>
+ <string name="restr_pin_error_doesnt_match" msgid="7063392698489280556">"PIN-ови се не подударају. Пробајте поново."</string>
+ <string name="restr_pin_error_too_short" msgid="1547007808237941065">"PIN је прекратак. Мора да садржи најмање 4 цифре."</string>
+ <string name="restr_pin_try_later" msgid="5897719962541636727">"Пробајте поново касније"</string>
+ <string name="immersive_cling_title" msgid="2307034298721541791">"Приказује се цео екран"</string>
+ <string name="immersive_cling_description" msgid="7092737175345204832">"Да бисте изашли, превуците надоле одозго."</string>
+ <string name="immersive_cling_positive" msgid="7047498036346489883">"Важи"</string>
+ <string name="done_label" msgid="7283767013231718521">"Готово"</string>
+ <string name="hour_picker_description" msgid="5153757582093524635">"Кружни клизач за сате"</string>
+ <string name="minute_picker_description" msgid="9029797023621927294">"Кружни клизач за минуте"</string>
+ <string name="select_hours" msgid="5982889657313147347">"Изаберите сате"</string>
+ <string name="select_minutes" msgid="9157401137441014032">"Изаберите минуте"</string>
+ <string name="select_day" msgid="2060371240117403147">"Изаберите месец и дан"</string>
+ <string name="select_year" msgid="1868350712095595393">"Изаберите годину"</string>
+ <string name="deleted_key" msgid="9130083334943364001">"Избрисали сте <xliff:g id="KEY">%1$s</xliff:g>"</string>
+ <string name="managed_profile_label_badge" msgid="6762559569999499495">"<xliff:g id="LABEL">%1$s</xliff:g> на послу"</string>
+ <string name="managed_profile_label_badge_2" msgid="5673187309555352550">"2. пословни <xliff:g id="LABEL">%1$s</xliff:g>"</string>
+ <string name="managed_profile_label_badge_3" msgid="6882151970556391957">"3. пословни имејл <xliff:g id="LABEL">%1$s</xliff:g>"</string>
+ <string name="lock_to_app_unlock_pin" msgid="3890940811866290782">"Тражи PIN пре откачињања"</string>
+ <string name="lock_to_app_unlock_pattern" msgid="2694204070499712503">"Тражи шаблон за откључавање пре откачињања"</string>
+ <string name="lock_to_app_unlock_password" msgid="9126722403506560473">"Тражи лозинку пре откачињања"</string>
+ <string name="package_installed_device_owner" msgid="7035926868974878525">"Инсталирао је администратор"</string>
+ <string name="package_updated_device_owner" msgid="7560272363805506941">"Ажурирао је администратор"</string>
+ <string name="package_deleted_device_owner" msgid="2292335928930293023">"Избрисао је администратор"</string>
+ <string name="confirm_battery_saver" msgid="5247976246208245754">"Потврди"</string>
+ <string name="battery_saver_description_with_learn_more" msgid="5444908404021316250">"Уштеда батерије укључује тамну тему и ограничава или искључује активности у позадини, неке визуелне ефекте, одређене функције и неке мрежне везе."</string>
+ <string name="battery_saver_description" msgid="8518809702138617167">"Уштеда батерије укључује тамну тему и ограничава или искључује активности у позадини, неке визуелне ефекте, одређене функције и неке мрежне везе."</string>
+ <string name="data_saver_description" msgid="4995164271550590517">"Да би се смањила потрошња података, Уштеда података спречава неке апликације да шаљу или примају податке у позадини. Апликација коју тренутно користите може да приступа подацима, али ће то чинити ређе. На пример, слике се неће приказивати док их не додирнете."</string>
+ <string name="data_saver_enable_title" msgid="7080620065745260137">"Желите да укључите Уштеду података?"</string>
+ <string name="data_saver_enable_button" msgid="4399405762586419726">"Укључи"</string>
+ <string name="zen_mode_duration_minutes_summary" msgid="4555514757230849789">"{count,plural, =1{Један минут (до {formattedTime})}one{# минут (до {formattedTime})}few{# минута (до {formattedTime})}other{# минута (до {formattedTime})}}"</string>
+ <string name="zen_mode_duration_minutes_summary_short" msgid="1187553788355486950">"{count,plural, =1{1 мин (до {formattedTime})}one{# мин (до {formattedTime})}few{# мин (до {formattedTime})}other{# мин (до {formattedTime})}}"</string>
+ <string name="zen_mode_duration_hours_summary" msgid="3866333100793277211">"{count,plural, =1{1 сат (до {formattedTime})}one{# сат (до {formattedTime})}few{# сата (до {formattedTime})}other{# сати (до {formattedTime})}}"</string>
+ <string name="zen_mode_duration_hours_summary_short" msgid="687919813833347945">"{count,plural, =1{1 с (до {formattedTime})}one{# с (до {formattedTime})}few{# с (до {formattedTime})}other{# с (до {formattedTime})}}"</string>
+ <string name="zen_mode_duration_minutes" msgid="2340007982276569054">"{count,plural, =1{Један минут}one{# минут}few{# минута}other{# минута}}"</string>
+ <string name="zen_mode_duration_minutes_short" msgid="2435756450204526554">"{count,plural, =1{1 мин}one{# мин}few{# мин}other{# мин}}"</string>
+ <string name="zen_mode_duration_hours" msgid="7841806065034711849">"{count,plural, =1{1 сат}one{# сат}few{# сата}other{# сати}}"</string>
+ <string name="zen_mode_duration_hours_short" msgid="3666949653933099065">"{count,plural, =1{1 с}one{# с}few{# с}other{# с}}"</string>
+ <string name="zen_mode_until_next_day" msgid="1403042784161725038">"До <xliff:g id="FORMATTEDTIME">%1$s</xliff:g>"</string>
+ <string name="zen_mode_until" msgid="2250286190237669079">"До <xliff:g id="FORMATTEDTIME">%1$s</xliff:g>"</string>
+ <string name="zen_mode_alarm" msgid="7046911727540499275">"До <xliff:g id="FORMATTEDTIME">%1$s</xliff:g> (следећи аларм)"</string>
+ <string name="zen_mode_forever" msgid="740585666364912448">"Док не искључите"</string>
+ <string name="zen_mode_forever_dnd" msgid="3423201955704180067">"Док не искључите режим Не узнемиравај"</string>
<string name="zen_mode_rule_name_combination" msgid="7174598364351313725">"<xliff:g id="FIRST">%1$s</xliff:g>/<xliff:g id="REST">%2$s</xliff:g>"</string>
- <string name="toolbar_collapse_description" msgid="8009920446193610996">"Skupi"</string>
- <string name="zen_mode_feature_name" msgid="3785547207263754500">"Ne uznemiravaj"</string>
- <string name="zen_mode_downtime_feature_name" msgid="5886005761431427128">"Odmor"</string>
- <string name="zen_mode_default_weeknights_name" msgid="7902108149994062847">"Radni dan uveče"</string>
- <string name="zen_mode_default_weekends_name" msgid="4707200272709377930">"Vikend"</string>
- <string name="zen_mode_default_events_name" msgid="2280682960128512257">"Događaj"</string>
- <string name="zen_mode_default_every_night_name" msgid="1467765312174275823">"Spavanje"</string>
- <string name="muted_by" msgid="91464083490094950">"<xliff:g id="THIRD_PARTY">%1$s</xliff:g> isključuje neke zvuke"</string>
- <string name="system_error_wipe_data" msgid="5910572292172208493">"Došlo je do internog problema u vezi sa uređajem i možda će biti nestabilan dok ne obavite resetovanje na fabrička podešavanja."</string>
- <string name="system_error_manufacturer" msgid="703545241070116315">"Došlo je do internog problema u vezi sa uređajem. Potražite detalje od proizvođača."</string>
- <string name="stk_cc_ussd_to_dial" msgid="3139884150741157610">"USSD zahtev je promenjen u običan poziv"</string>
- <string name="stk_cc_ussd_to_ss" msgid="4826846653052609738">"USSD zahtev je promenjen u SS zahtev"</string>
- <string name="stk_cc_ussd_to_ussd" msgid="8343001461299302472">"Promenjeno je u novi USSD zahtev"</string>
- <string name="stk_cc_ussd_to_dial_video" msgid="429118590323618623">"USSD zahtev je promenjen u video poziv"</string>
- <string name="stk_cc_ss_to_dial" msgid="4087396658768717077">"SS zahtev je promenjen u običan poziv"</string>
- <string name="stk_cc_ss_to_dial_video" msgid="1324194624384312664">"SS zahtev je promenjen u video poziv"</string>
- <string name="stk_cc_ss_to_ussd" msgid="8417905193112944760">"SS zahtev je promenjen u USSD zahtev"</string>
- <string name="stk_cc_ss_to_ss" msgid="132040645206514450">"Promenjeno je u novi SS zahtev"</string>
- <string name="notification_phishing_alert_content_description" msgid="494227305355958790">"Upozorenje o „pecanju“"</string>
- <string name="notification_work_profile_content_description" msgid="5296477955677725799">"Poslovni profil"</string>
- <string name="notification_alerted_content_description" msgid="6139691253611265992">"Obavešteno"</string>
- <string name="notification_verified_content_description" msgid="6401483602782359391">"Verifikovano"</string>
- <string name="expand_button_content_description_collapsed" msgid="3873368935659010279">"Proširi"</string>
- <string name="expand_button_content_description_expanded" msgid="7484217944948667489">"Skupi"</string>
- <string name="expand_action_accessibility" msgid="1947657036871746627">"uključite/isključite proširenje"</string>
- <string name="usb_midi_peripheral_name" msgid="490523464968655741">"Android USB port za periferijske uređaje"</string>
+ <string name="toolbar_collapse_description" msgid="8009920446193610996">"Скупи"</string>
+ <string name="zen_mode_feature_name" msgid="3785547207263754500">"Не узнемиравај"</string>
+ <string name="zen_mode_downtime_feature_name" msgid="5886005761431427128">"Одмор"</string>
+ <string name="zen_mode_default_weeknights_name" msgid="7902108149994062847">"Радни дан увече"</string>
+ <string name="zen_mode_default_weekends_name" msgid="4707200272709377930">"Викенд"</string>
+ <string name="zen_mode_default_events_name" msgid="2280682960128512257">"Догађај"</string>
+ <string name="zen_mode_default_every_night_name" msgid="1467765312174275823">"Спавање"</string>
+ <string name="muted_by" msgid="91464083490094950">"<xliff:g id="THIRD_PARTY">%1$s</xliff:g> искључује неке звуке"</string>
+ <string name="system_error_wipe_data" msgid="5910572292172208493">"Дошло је до интерног проблема у вези са уређајем и можда ће бити нестабилан док не обавите ресетовање на фабричка подешавања."</string>
+ <string name="system_error_manufacturer" msgid="703545241070116315">"Дошло је до интерног проблема у вези са уређајем. Потражите детаље од произвођача."</string>
+ <string name="stk_cc_ussd_to_dial" msgid="3139884150741157610">"USSD захтев је промењен у обичан позив"</string>
+ <string name="stk_cc_ussd_to_ss" msgid="4826846653052609738">"USSD захтев је промењен у SS захтев"</string>
+ <string name="stk_cc_ussd_to_ussd" msgid="8343001461299302472">"Промењено је у нови USSD захтев"</string>
+ <string name="stk_cc_ussd_to_dial_video" msgid="429118590323618623">"USSD захтев је промењен у видео позив"</string>
+ <string name="stk_cc_ss_to_dial" msgid="4087396658768717077">"SS захтев је промењен у обичан позив"</string>
+ <string name="stk_cc_ss_to_dial_video" msgid="1324194624384312664">"SS захтев је промењен у видео позив"</string>
+ <string name="stk_cc_ss_to_ussd" msgid="8417905193112944760">"SS захтев је промењен у USSD захтев"</string>
+ <string name="stk_cc_ss_to_ss" msgid="132040645206514450">"Промењено је у нови SS захтев"</string>
+ <string name="notification_phishing_alert_content_description" msgid="494227305355958790">"Упозорење о „пецању“"</string>
+ <string name="notification_work_profile_content_description" msgid="5296477955677725799">"Пословни профил"</string>
+ <string name="notification_alerted_content_description" msgid="6139691253611265992">"Обавештено"</string>
+ <string name="notification_verified_content_description" msgid="6401483602782359391">"Верификовано"</string>
+ <string name="expand_button_content_description_collapsed" msgid="3873368935659010279">"Прошири"</string>
+ <string name="expand_button_content_description_expanded" msgid="7484217944948667489">"Скупи"</string>
+ <string name="expand_action_accessibility" msgid="1947657036871746627">"укључите/искључите проширење"</string>
+ <string name="usb_midi_peripheral_name" msgid="490523464968655741">"Android USB порт за периферијске уређаје"</string>
<string name="usb_midi_peripheral_manufacturer_name" msgid="7557148557088787741">"Android"</string>
- <string name="usb_midi_peripheral_product_name" msgid="2836276258480904434">"USB port za periferijske uređaje"</string>
- <string name="floating_toolbar_open_overflow_description" msgid="2260297653578167367">"Još opcija"</string>
- <string name="floating_toolbar_close_overflow_description" msgid="3949818077708138098">"Zatvori preklopni meni"</string>
- <string name="maximize_button_text" msgid="4258922519914732645">"Uvećaj"</string>
- <string name="close_button_text" msgid="10603510034455258">"Zatvori"</string>
+ <string name="usb_midi_peripheral_product_name" msgid="2836276258480904434">"USB порт за периферијске уређаје"</string>
+ <string name="floating_toolbar_open_overflow_description" msgid="2260297653578167367">"Још опција"</string>
+ <string name="floating_toolbar_close_overflow_description" msgid="3949818077708138098">"Затвори преклопни мени"</string>
+ <string name="maximize_button_text" msgid="4258922519914732645">"Увећај"</string>
+ <string name="close_button_text" msgid="10603510034455258">"Затвори"</string>
<string name="notification_messaging_title_template" msgid="772857526770251989">"<xliff:g id="CONVERSATION_TITLE">%1$s</xliff:g>: <xliff:g id="SENDER_NAME">%2$s</xliff:g>"</string>
- <string name="call_notification_answer_action" msgid="5999246836247132937">"Odgovori"</string>
- <string name="call_notification_answer_video_action" msgid="2086030940195382249">"Video"</string>
- <string name="call_notification_decline_action" msgid="3700345945214000726">"Odbij"</string>
- <string name="call_notification_hang_up_action" msgid="9130720590159188131">"Prekini vezu"</string>
- <string name="call_notification_incoming_text" msgid="6143109825406638201">"Dolazni poziv"</string>
- <string name="call_notification_ongoing_text" msgid="3880832933933020875">"Poziv je u toku"</string>
- <string name="call_notification_screening_text" msgid="8396931408268940208">"Proverava se dolazni poziv"</string>
- <string name="default_notification_channel_label" msgid="3697928973567217330">"Nekategorizovano"</string>
- <string name="importance_from_user" msgid="2782756722448800447">"Vi podešavate važnost ovih obaveštenja."</string>
- <string name="importance_from_person" msgid="4235804979664465383">"Ovo je važno zbog ljudi koji učestvuju."</string>
- <string name="notification_history_title_placeholder" msgid="7748630986182249599">"Prilagođeno obaveštenje o aplikaciji"</string>
- <string name="user_creation_account_exists" msgid="2239146360099708035">"Želite li da dozvolite da <xliff:g id="APP">%1$s</xliff:g> napravi novog korisnika sa nalogom <xliff:g id="ACCOUNT">%2$s</xliff:g> (korisnik sa tim nalogom već postoji)?"</string>
- <string name="user_creation_adding" msgid="7305185499667958364">"Želite li da dozvolite da <xliff:g id="APP">%1$s</xliff:g> napravi novog korisnika sa nalogom <xliff:g id="ACCOUNT">%2$s</xliff:g>?"</string>
- <string name="supervised_user_creation_label" msgid="6884904353827427515">"Dodajte korisnika pod nadzorom"</string>
- <string name="language_selection_title" msgid="52674936078683285">"Dodajte jezik"</string>
- <string name="country_selection_title" msgid="5221495687299014379">"Podešavanje regiona"</string>
- <string name="search_language_hint" msgid="7004225294308793583">"Unesite naziv jezika"</string>
- <string name="language_picker_section_suggested" msgid="6556199184638990447">"Predloženi"</string>
- <string name="language_picker_regions_section_suggested" msgid="6080131515268225316">"Predloženo"</string>
- <string name="language_picker_section_suggested_bilingual" msgid="5932198319583556613">"Predloženi jezici"</string>
- <string name="region_picker_section_suggested_bilingual" msgid="704607569328224133">"Predloženi regioni"</string>
- <string name="language_picker_section_all" msgid="1985809075777564284">"Svi jezici"</string>
- <string name="region_picker_section_all" msgid="756441309928774155">"Svi regioni"</string>
- <string name="locale_search_menu" msgid="6258090710176422934">"Pretraži"</string>
- <string name="app_suspended_title" msgid="888873445010322650">"Aplikacija nije dostupna"</string>
- <string name="app_suspended_default_message" msgid="6451215678552004172">"Aplikacija <xliff:g id="APP_NAME_0">%1$s</xliff:g> trenutno nije dostupna. <xliff:g id="APP_NAME_1">%2$s</xliff:g> upravlja dostupnošću."</string>
- <string name="app_suspended_more_details" msgid="211260942831587014">"Saznajte više"</string>
- <string name="app_suspended_unsuspend_message" msgid="1665438589450555459">"Opozovi pauziranje aplikacije"</string>
- <string name="work_mode_off_title" msgid="961171256005852058">"Uključujete poslovne aplikacije?"</string>
- <string name="work_mode_off_message" msgid="7319580997683623309">"Pristupajte poslovnim aplikacijama i obaveštenjima"</string>
- <string name="work_mode_turn_on" msgid="3662561662475962285">"Uključi"</string>
- <string name="app_blocked_title" msgid="7353262160455028160">"Aplikacija nije dostupna"</string>
- <string name="app_blocked_message" msgid="542972921087873023">"Aplikacija <xliff:g id="APP_NAME">%1$s</xliff:g> trenutno nije dostupna."</string>
- <string name="app_streaming_blocked_title" msgid="6090945835898766139">"<xliff:g id="ACTIVITY">%1$s</xliff:g> – nije dostupno"</string>
- <string name="app_streaming_blocked_title_for_permission_dialog" msgid="4483161748582966785">"Potrebna je dozvola"</string>
- <string name="app_streaming_blocked_title_for_camera_dialog" msgid="3935701653713853065">"Kamera nije dostupna"</string>
- <string name="app_streaming_blocked_title_for_fingerprint_dialog" msgid="3516853717714141951">"Nastavite na telefonu"</string>
- <string name="app_streaming_blocked_title_for_microphone_dialog" msgid="544822455127171206">"Mikrofon je nedostupan"</string>
- <string name="app_streaming_blocked_title_for_playstore_dialog" msgid="8149823099822897538">"Play prodavnica nije dostupna"</string>
- <string name="app_streaming_blocked_title_for_settings_dialog" product="tv" msgid="196994247017450357">"Podešavanja Android TV-a su nedostupna"</string>
- <string name="app_streaming_blocked_title_for_settings_dialog" product="tablet" msgid="8222710146267948647">"Podešavanja tableta su nedostupna"</string>
- <string name="app_streaming_blocked_title_for_settings_dialog" product="default" msgid="6895719984375299791">"Podešavanja telefona su nedostupna"</string>
- <string name="app_streaming_blocked_message" product="tv" msgid="4003011766528814377">"Ovoj aplikaciji trenutno ne može da se pristupi sa uređaja <xliff:g id="DEVICE">%1$s</xliff:g>. Probajte na Android TV uređaju."</string>
- <string name="app_streaming_blocked_message" product="tablet" msgid="4242053045964946062">"Ovoj aplikaciji trenutno ne može da se pristupi sa uređaja <xliff:g id="DEVICE">%1$s</xliff:g>. Probajte na tabletu."</string>
- <string name="app_streaming_blocked_message" product="default" msgid="6159168735030739398">"Ovoj aplikaciji trenutno ne može da se pristupi sa uređaja <xliff:g id="DEVICE">%1$s</xliff:g>. Probajte na telefonu."</string>
- <string name="app_streaming_blocked_message_for_fingerprint_dialog" product="tv" msgid="3470977315395784567">"Ova aplikacija zahteva dodatnu bezbednost. Probajte na Android TV uređaju."</string>
- <string name="app_streaming_blocked_message_for_fingerprint_dialog" product="tablet" msgid="698460091901465092">"Ova aplikacija zahteva dodatnu bezbednost. Probajte na tabletu."</string>
- <string name="app_streaming_blocked_message_for_fingerprint_dialog" product="default" msgid="8552691971910603907">"Ova aplikacija zahteva dodatnu bezbednost. Probajte na telefonu."</string>
- <string name="app_streaming_blocked_message_for_settings_dialog" product="tv" msgid="820334666354451145">"Ovoj aplikaciji ne može da se pristupi sa uređaja <xliff:g id="DEVICE">%1$s</xliff:g>. Probajte na Android TV uređaju."</string>
- <string name="app_streaming_blocked_message_for_settings_dialog" product="tablet" msgid="3286849551133045896">"Ovoj aplikaciji ne može da se pristupi sa uređaja <xliff:g id="DEVICE">%1$s</xliff:g>. Probajte na tabletu."</string>
- <string name="app_streaming_blocked_message_for_settings_dialog" product="default" msgid="6264287556598916295">"Ovoj aplikaciji ne može da se pristupi sa uređaja <xliff:g id="DEVICE">%1$s</xliff:g>. Probajte na telefonu."</string>
- <string name="deprecated_target_sdk_message" msgid="5246906284426844596">"Ova aplikacija je napravljena za stariju verziju Android-a. Možda neće raditi ispravno i ne obuhvata najnovije bezbednosne funkcije i zaštite privatnosti. Proverite da li ima ažuriranja ili se obratite programeru aplikacije."</string>
- <string name="deprecated_target_sdk_app_store" msgid="8456784048558808909">"Potraži ažuriranje"</string>
- <string name="new_sms_notification_title" msgid="6528758221319927107">"Imate nove poruke"</string>
- <string name="new_sms_notification_content" msgid="3197949934153460639">"Otvorite aplikaciju za SMS da biste pregledali"</string>
- <string name="profile_encrypted_title" msgid="9001208667521266472">"Neke funkcije su možda ograničene"</string>
- <string name="profile_encrypted_detail" msgid="5279730442756849055">"Poslovni profil je zaključan"</string>
- <string name="profile_encrypted_message" msgid="1128512616293157802">"Dodirom otklj. poslovni profil"</string>
- <string name="usb_mtp_launch_notification_title" msgid="774319638256707227">"Povezano je sa proizvodom <xliff:g id="PRODUCT_NAME">%1$s</xliff:g>"</string>
- <string name="usb_mtp_launch_notification_description" msgid="6942535713629852684">"Dodirnite za pregled datoteka"</string>
- <string name="pin_target" msgid="8036028973110156895">"Zakači"</string>
- <string name="pin_specific_target" msgid="7824671240625957415">"Zakači aplikaciju <xliff:g id="LABEL">%1$s</xliff:g>"</string>
- <string name="unpin_target" msgid="3963318576590204447">"Otkači"</string>
- <string name="unpin_specific_target" msgid="3859828252160908146">"Otkači aplikaciju <xliff:g id="LABEL">%1$s</xliff:g>"</string>
- <string name="app_info" msgid="6113278084877079851">"Informacije o aplikaciji"</string>
+ <string name="call_notification_answer_action" msgid="5999246836247132937">"Одговори"</string>
+ <string name="call_notification_answer_video_action" msgid="2086030940195382249">"Видео"</string>
+ <string name="call_notification_decline_action" msgid="3700345945214000726">"Одбиј"</string>
+ <string name="call_notification_hang_up_action" msgid="9130720590159188131">"Прекини везу"</string>
+ <string name="call_notification_incoming_text" msgid="6143109825406638201">"Долазни позив"</string>
+ <string name="call_notification_ongoing_text" msgid="3880832933933020875">"Позив је у току"</string>
+ <string name="call_notification_screening_text" msgid="8396931408268940208">"Проверава се долазни позив"</string>
+ <string name="default_notification_channel_label" msgid="3697928973567217330">"Некатегоризовано"</string>
+ <string name="importance_from_user" msgid="2782756722448800447">"Ви подешавате важност ових обавештења."</string>
+ <string name="importance_from_person" msgid="4235804979664465383">"Ово је важно због људи који учествују."</string>
+ <string name="notification_history_title_placeholder" msgid="7748630986182249599">"Прилагођено обавештење о апликацији"</string>
+ <string name="user_creation_account_exists" msgid="2239146360099708035">"Желите ли да дозволите да <xliff:g id="APP">%1$s</xliff:g> направи новог корисника са налогом <xliff:g id="ACCOUNT">%2$s</xliff:g> (корисник са тим налогом већ постоји)?"</string>
+ <string name="user_creation_adding" msgid="7305185499667958364">"Желите ли да дозволите да <xliff:g id="APP">%1$s</xliff:g> направи новог корисника са налогом <xliff:g id="ACCOUNT">%2$s</xliff:g>?"</string>
+ <string name="supervised_user_creation_label" msgid="6884904353827427515">"Додајте корисника под надзором"</string>
+ <string name="language_selection_title" msgid="52674936078683285">"Додајте језик"</string>
+ <string name="country_selection_title" msgid="5221495687299014379">"Подешавање региона"</string>
+ <string name="search_language_hint" msgid="7004225294308793583">"Унесите назив језика"</string>
+ <string name="language_picker_section_suggested" msgid="6556199184638990447">"Предложени"</string>
+ <string name="language_picker_regions_section_suggested" msgid="6080131515268225316">"Предложено"</string>
+ <string name="language_picker_section_suggested_bilingual" msgid="5932198319583556613">"Предложени језици"</string>
+ <string name="region_picker_section_suggested_bilingual" msgid="704607569328224133">"Предложени региони"</string>
+ <string name="language_picker_section_all" msgid="1985809075777564284">"Сви језици"</string>
+ <string name="region_picker_section_all" msgid="756441309928774155">"Сви региони"</string>
+ <string name="locale_search_menu" msgid="6258090710176422934">"Претражи"</string>
+ <string name="app_suspended_title" msgid="888873445010322650">"Апликација није доступна"</string>
+ <string name="app_suspended_default_message" msgid="6451215678552004172">"Апликација <xliff:g id="APP_NAME_0">%1$s</xliff:g> тренутно није доступна. <xliff:g id="APP_NAME_1">%2$s</xliff:g> управља доступношћу."</string>
+ <string name="app_suspended_more_details" msgid="211260942831587014">"Сазнајте више"</string>
+ <string name="app_suspended_unsuspend_message" msgid="1665438589450555459">"Опозови паузирање апликације"</string>
+ <string name="work_mode_off_title" msgid="961171256005852058">"Укључујете пословне апликације?"</string>
+ <string name="work_mode_off_message" msgid="7319580997683623309">"Приступајте пословним апликацијама и обавештењима"</string>
+ <string name="work_mode_turn_on" msgid="3662561662475962285">"Укључи"</string>
+ <string name="app_blocked_title" msgid="7353262160455028160">"Апликација није доступна"</string>
+ <string name="app_blocked_message" msgid="542972921087873023">"Апликација <xliff:g id="APP_NAME">%1$s</xliff:g> тренутно није доступна."</string>
+ <string name="app_streaming_blocked_title" msgid="6090945835898766139">"<xliff:g id="ACTIVITY">%1$s</xliff:g> – није доступно"</string>
+ <string name="app_streaming_blocked_title_for_permission_dialog" msgid="4483161748582966785">"Потребна је дозвола"</string>
+ <string name="app_streaming_blocked_title_for_camera_dialog" msgid="3935701653713853065">"Камера није доступна"</string>
+ <string name="app_streaming_blocked_title_for_fingerprint_dialog" msgid="3516853717714141951">"Наставите на телефону"</string>
+ <string name="app_streaming_blocked_title_for_microphone_dialog" msgid="544822455127171206">"Микрофон је недоступан"</string>
+ <string name="app_streaming_blocked_title_for_playstore_dialog" msgid="8149823099822897538">"Play продавница није доступна"</string>
+ <string name="app_streaming_blocked_title_for_settings_dialog" product="tv" msgid="196994247017450357">"Подешавања Android TV-а су недоступна"</string>
+ <string name="app_streaming_blocked_title_for_settings_dialog" product="tablet" msgid="8222710146267948647">"Подешавања таблета су недоступна"</string>
+ <string name="app_streaming_blocked_title_for_settings_dialog" product="default" msgid="6895719984375299791">"Подешавања телефона су недоступна"</string>
+ <string name="app_streaming_blocked_message" product="tv" msgid="4003011766528814377">"Овој апликацији тренутно не може да се приступи са уређаја <xliff:g id="DEVICE">%1$s</xliff:g>. Пробајте на Android TV уређају."</string>
+ <string name="app_streaming_blocked_message" product="tablet" msgid="4242053045964946062">"Овој апликацији тренутно не може да се приступи са уређаја <xliff:g id="DEVICE">%1$s</xliff:g>. Пробајте на таблету."</string>
+ <string name="app_streaming_blocked_message" product="default" msgid="6159168735030739398">"Овој апликацији тренутно не може да се приступи са уређаја <xliff:g id="DEVICE">%1$s</xliff:g>. Пробајте на телефону."</string>
+ <string name="app_streaming_blocked_message_for_fingerprint_dialog" product="tv" msgid="3470977315395784567">"Ова апликација захтева додатну безбедност. Пробајте на Android TV уређају."</string>
+ <string name="app_streaming_blocked_message_for_fingerprint_dialog" product="tablet" msgid="698460091901465092">"Ова апликација захтева додатну безбедност. Пробајте на таблету."</string>
+ <string name="app_streaming_blocked_message_for_fingerprint_dialog" product="default" msgid="8552691971910603907">"Ова апликација захтева додатну безбедност. Пробајте на телефону."</string>
+ <string name="app_streaming_blocked_message_for_settings_dialog" product="tv" msgid="820334666354451145">"Овој апликацији не може да се приступи са уређаја <xliff:g id="DEVICE">%1$s</xliff:g>. Пробајте на Android TV уређају."</string>
+ <string name="app_streaming_blocked_message_for_settings_dialog" product="tablet" msgid="3286849551133045896">"Овој апликацији не може да се приступи са уређаја <xliff:g id="DEVICE">%1$s</xliff:g>. Пробајте на таблету."</string>
+ <string name="app_streaming_blocked_message_for_settings_dialog" product="default" msgid="6264287556598916295">"Овој апликацији не може да се приступи са уређаја <xliff:g id="DEVICE">%1$s</xliff:g>. Пробајте на телефону."</string>
+ <string name="deprecated_target_sdk_message" msgid="5246906284426844596">"Ова апликација је направљена за старију верзију Android-а. Можда неће радити исправно и не обухвата најновије безбедносне функције и заштите приватности. Проверите да ли има ажурирања или се обратите програмеру апликације."</string>
+ <string name="deprecated_target_sdk_app_store" msgid="8456784048558808909">"Потражи ажурирање"</string>
+ <string name="new_sms_notification_title" msgid="6528758221319927107">"Имате нове поруке"</string>
+ <string name="new_sms_notification_content" msgid="3197949934153460639">"Отворите апликацију за SMS да бисте прегледали"</string>
+ <string name="profile_encrypted_title" msgid="9001208667521266472">"Неке функције су можда ограничене"</string>
+ <string name="profile_encrypted_detail" msgid="5279730442756849055">"Пословни профил је закључан"</string>
+ <string name="profile_encrypted_message" msgid="1128512616293157802">"Додиром откљ. пословни профил"</string>
+ <string name="usb_mtp_launch_notification_title" msgid="774319638256707227">"Повезано је са производом <xliff:g id="PRODUCT_NAME">%1$s</xliff:g>"</string>
+ <string name="usb_mtp_launch_notification_description" msgid="6942535713629852684">"Додирните за преглед датотека"</string>
+ <string name="pin_target" msgid="8036028973110156895">"Закачи"</string>
+ <string name="pin_specific_target" msgid="7824671240625957415">"Закачи апликацију <xliff:g id="LABEL">%1$s</xliff:g>"</string>
+ <string name="unpin_target" msgid="3963318576590204447">"Откачи"</string>
+ <string name="unpin_specific_target" msgid="3859828252160908146">"Откачи апликацију <xliff:g id="LABEL">%1$s</xliff:g>"</string>
+ <string name="app_info" msgid="6113278084877079851">"Информације о апликацији"</string>
<string name="negative_duration" msgid="1938335096972945232">"−<xliff:g id="TIME">%1$s</xliff:g>"</string>
- <string name="demo_starting_message" msgid="6577581216125805905">"Pokrećemo demonstraciju..."</string>
- <string name="demo_restarting_message" msgid="1160053183701746766">"Resetujemo uređaj..."</string>
- <string name="suspended_widget_accessibility" msgid="6331451091851326101">"Vidžet <xliff:g id="LABEL">%1$s</xliff:g> je onemogućen"</string>
- <string name="conference_call" msgid="5731633152336490471">"Konferencijski poziv"</string>
- <string name="tooltip_popup_title" msgid="7863719020269945722">"Objašnjenje"</string>
- <string name="app_category_game" msgid="4534216074910244790">"Igre"</string>
- <string name="app_category_audio" msgid="8296029904794676222">"Muzika i audio"</string>
- <string name="app_category_video" msgid="2590183854839565814">"Filmovi i video"</string>
- <string name="app_category_image" msgid="7307840291864213007">"Slike"</string>
- <string name="app_category_social" msgid="2278269325488344054">"Društvene mreže i komunikacija"</string>
- <string name="app_category_news" msgid="1172762719574964544">"Novosti i časopisi"</string>
- <string name="app_category_maps" msgid="6395725487922533156">"Mape i navigacija"</string>
- <string name="app_category_productivity" msgid="1844422703029557883">"Produktivnost"</string>
- <string name="app_category_accessibility" msgid="6643521607848547683">"Pristupačnost"</string>
- <string name="device_storage_monitor_notification_channel" msgid="5164244565844470758">"Memorijski prostor uređaja"</string>
- <string name="adb_debugging_notification_channel_tv" msgid="4764046459631031496">"Otklanjanje grešaka sa USB-a"</string>
- <string name="time_picker_hour_label" msgid="4208590187662336864">"sat"</string>
- <string name="time_picker_minute_label" msgid="8307452311269824553">"minut"</string>
- <string name="time_picker_header_text" msgid="9073802285051516688">"Podesite vreme"</string>
- <string name="time_picker_input_error" msgid="8386271930742451034">"Unesite važeće vreme"</string>
- <string name="time_picker_prompt_label" msgid="303588544656363889">"Unesite vreme"</string>
- <string name="time_picker_text_input_mode_description" msgid="4761160667516611576">"Pređite u režim unosa teksta radi unosa vremena."</string>
- <string name="time_picker_radial_mode_description" msgid="1222342577115016953">"Pređite u režim sata radi unosa vremena."</string>
- <string name="autofill_picker_accessibility_title" msgid="4425806874792196599">"Opcije automatskog popunjavanja"</string>
- <string name="autofill_save_accessibility_title" msgid="1523225776218450005">"Sačuvajte za automatsko popunjavanje"</string>
- <string name="autofill_error_cannot_autofill" msgid="6528827648643138596">"Sadržaj ne može automatski da se popuni"</string>
- <string name="autofill_picker_no_suggestions" msgid="1076022650427481509">"Nema automatski popunjenih predloga"</string>
- <string name="autofill_picker_some_suggestions" msgid="5560549696296202701">"{count,plural, =1{Jedan automatski popunjen predlog}one{# automatski popunjen predlog}few{# automatski popunjena predloga}other{# automatski popunjenih predloga}}"</string>
- <string name="autofill_save_title" msgid="7719802414283739775">"Želite li da sačuvate u usluzi "<b>"<xliff:g id="LABEL">%1$s</xliff:g>"</b>"?"</string>
- <string name="autofill_save_title_with_type" msgid="3002460014579799605">"Želite li da sačuvate stavku <xliff:g id="TYPE">%1$s</xliff:g> u usluzi "<b>"<xliff:g id="LABEL">%2$s</xliff:g>"</b>"?"</string>
- <string name="autofill_save_title_with_2types" msgid="3783270967447869241">"Želite li da sačuvate stavke <xliff:g id="TYPE_0">%1$s</xliff:g> i <xliff:g id="TYPE_1">%2$s</xliff:g> u usluzi "<b>"<xliff:g id="LABEL">%3$s</xliff:g>"</b>"?"</string>
- <string name="autofill_save_title_with_3types" msgid="6598228952100102578">"Želite li da sačuvate stavke <xliff:g id="TYPE_0">%1$s</xliff:g>, <xliff:g id="TYPE_1">%2$s</xliff:g> i <xliff:g id="TYPE_2">%3$s</xliff:g> u usluzi "<b>"<xliff:g id="LABEL">%4$s</xliff:g>"</b>"?"</string>
- <string name="autofill_update_title" msgid="3630695947047069136">"Želite li da ažurirate u usluzi "<b>"<xliff:g id="LABEL">%1$s</xliff:g>"</b>"?"</string>
- <string name="autofill_update_title_with_type" msgid="5264152633488495704">"Želite li da ažurirate stavku <xliff:g id="TYPE">%1$s</xliff:g> u usluzi "<b>"<xliff:g id="LABEL">%2$s</xliff:g>"</b>"?"</string>
- <string name="autofill_update_title_with_2types" msgid="1797514386321086273">"Želite li da ažurirate stavke <xliff:g id="TYPE_0">%1$s</xliff:g> i <xliff:g id="TYPE_1">%2$s</xliff:g> u usluzi "<b>"<xliff:g id="LABEL">%3$s</xliff:g>"</b>"?"</string>
- <string name="autofill_update_title_with_3types" msgid="1312232153076212291">"Želite li da ažurirate ove stavke u usluzi "<b>"<xliff:g id="LABEL">%4$s</xliff:g>"</b>": <xliff:g id="TYPE_0">%1$s</xliff:g>, <xliff:g id="TYPE_1">%2$s</xliff:g> i <xliff:g id="TYPE_2">%3$s</xliff:g>?"</string>
- <string name="autofill_save_yes" msgid="8035743017382012850">"Sačuvaj"</string>
- <string name="autofill_save_no" msgid="9212826374207023544">"Ne, hvala"</string>
- <string name="autofill_save_notnow" msgid="2853932672029024195">"Ne sada"</string>
- <string name="autofill_save_never" msgid="6821841919831402526">"Nikada"</string>
- <string name="autofill_update_yes" msgid="4608662968996874445">"Ažuriraj"</string>
- <string name="autofill_continue_yes" msgid="7914985605534510385">"Nastavi"</string>
- <string name="autofill_save_type_password" msgid="5624528786144539944">"lozinka"</string>
- <string name="autofill_save_type_address" msgid="3111006395818252885">"adresa"</string>
- <string name="autofill_save_type_credit_card" msgid="3583795235862046693">"kreditna kartica"</string>
- <string name="autofill_save_type_debit_card" msgid="3169397504133097468">"debitna kartica"</string>
- <string name="autofill_save_type_payment_card" msgid="6555012156728690856">"platna kartica"</string>
- <string name="autofill_save_type_generic_card" msgid="1019367283921448608">"kartica"</string>
- <string name="autofill_save_type_username" msgid="1018816929884640882">"korisničko ime"</string>
- <string name="autofill_save_type_email_address" msgid="1303262336895591924">"imejl adresa"</string>
- <string name="etws_primary_default_message_earthquake" msgid="8401079517718280669">"Ostanite mirni i potražite sklonište u okolini."</string>
- <string name="etws_primary_default_message_tsunami" msgid="5828171463387976279">"Odmah se sklonite iz priobalnih regiona i oblasti pored reka na neko bezbednije mesto, na primer, na neko uzvišenje."</string>
- <string name="etws_primary_default_message_earthquake_and_tsunami" msgid="4888224011071875068">"Ostanite mirni i potražite sklonište u okolini."</string>
- <string name="etws_primary_default_message_test" msgid="4583367373909549421">"Testiranje poruka u hitnim slučajevima"</string>
- <string name="notification_reply_button_accessibility" msgid="5235776156579456126">"Odgovori"</string>
+ <string name="demo_starting_message" msgid="6577581216125805905">"Покрећемо демонстрацију..."</string>
+ <string name="demo_restarting_message" msgid="1160053183701746766">"Ресетујемо уређај..."</string>
+ <string name="suspended_widget_accessibility" msgid="6331451091851326101">"Виџет <xliff:g id="LABEL">%1$s</xliff:g> је онемогућен"</string>
+ <string name="conference_call" msgid="5731633152336490471">"Конференцијски позив"</string>
+ <string name="tooltip_popup_title" msgid="7863719020269945722">"Објашњење"</string>
+ <string name="app_category_game" msgid="4534216074910244790">"Игре"</string>
+ <string name="app_category_audio" msgid="8296029904794676222">"Музика и аудио"</string>
+ <string name="app_category_video" msgid="2590183854839565814">"Филмови и видео"</string>
+ <string name="app_category_image" msgid="7307840291864213007">"Слике"</string>
+ <string name="app_category_social" msgid="2278269325488344054">"Друштвене мреже и комуникација"</string>
+ <string name="app_category_news" msgid="1172762719574964544">"Новости и часописи"</string>
+ <string name="app_category_maps" msgid="6395725487922533156">"Мапе и навигација"</string>
+ <string name="app_category_productivity" msgid="1844422703029557883">"Продуктивност"</string>
+ <string name="app_category_accessibility" msgid="6643521607848547683">"Приступачност"</string>
+ <string name="device_storage_monitor_notification_channel" msgid="5164244565844470758">"Меморијски простор уређаја"</string>
+ <string name="adb_debugging_notification_channel_tv" msgid="4764046459631031496">"Отклањање грешака са USB-а"</string>
+ <string name="time_picker_hour_label" msgid="4208590187662336864">"сат"</string>
+ <string name="time_picker_minute_label" msgid="8307452311269824553">"минут"</string>
+ <string name="time_picker_header_text" msgid="9073802285051516688">"Подесите време"</string>
+ <string name="time_picker_input_error" msgid="8386271930742451034">"Унесите важеће време"</string>
+ <string name="time_picker_prompt_label" msgid="303588544656363889">"Унесите време"</string>
+ <string name="time_picker_text_input_mode_description" msgid="4761160667516611576">"Пређите у режим уноса текста ради уноса времена."</string>
+ <string name="time_picker_radial_mode_description" msgid="1222342577115016953">"Пређите у режим сата ради уноса времена."</string>
+ <string name="autofill_picker_accessibility_title" msgid="4425806874792196599">"Опције аутоматског попуњавања"</string>
+ <string name="autofill_save_accessibility_title" msgid="1523225776218450005">"Сачувајте за аутоматско попуњавање"</string>
+ <string name="autofill_error_cannot_autofill" msgid="6528827648643138596">"Садржај не може аутоматски да се попуни"</string>
+ <string name="autofill_picker_no_suggestions" msgid="1076022650427481509">"Нема аутоматски попуњених предлога"</string>
+ <string name="autofill_picker_some_suggestions" msgid="5560549696296202701">"{count,plural, =1{Један аутоматски попуњен предлог}one{# аутоматски попуњен предлог}few{# аутоматски попуњена предлога}other{# аутоматски попуњених предлога}}"</string>
+ <string name="autofill_save_title" msgid="7719802414283739775">"Желите ли да сачувате у услузи "<b>"<xliff:g id="LABEL">%1$s</xliff:g>"</b>"?"</string>
+ <string name="autofill_save_title_with_type" msgid="3002460014579799605">"Желите ли да сачувате ставку <xliff:g id="TYPE">%1$s</xliff:g> у услузи "<b>"<xliff:g id="LABEL">%2$s</xliff:g>"</b>"?"</string>
+ <string name="autofill_save_title_with_2types" msgid="3783270967447869241">"Желите ли да сачувате ставке <xliff:g id="TYPE_0">%1$s</xliff:g> и <xliff:g id="TYPE_1">%2$s</xliff:g> у услузи "<b>"<xliff:g id="LABEL">%3$s</xliff:g>"</b>"?"</string>
+ <string name="autofill_save_title_with_3types" msgid="6598228952100102578">"Желите ли да сачувате ставке <xliff:g id="TYPE_0">%1$s</xliff:g>, <xliff:g id="TYPE_1">%2$s</xliff:g> и <xliff:g id="TYPE_2">%3$s</xliff:g> у услузи "<b>"<xliff:g id="LABEL">%4$s</xliff:g>"</b>"?"</string>
+ <string name="autofill_update_title" msgid="3630695947047069136">"Желите ли да ажурирате у услузи "<b>"<xliff:g id="LABEL">%1$s</xliff:g>"</b>"?"</string>
+ <string name="autofill_update_title_with_type" msgid="5264152633488495704">"Желите ли да ажурирате ставку <xliff:g id="TYPE">%1$s</xliff:g> у услузи "<b>"<xliff:g id="LABEL">%2$s</xliff:g>"</b>"?"</string>
+ <string name="autofill_update_title_with_2types" msgid="1797514386321086273">"Желите ли да ажурирате ставке <xliff:g id="TYPE_0">%1$s</xliff:g> и <xliff:g id="TYPE_1">%2$s</xliff:g> у услузи "<b>"<xliff:g id="LABEL">%3$s</xliff:g>"</b>"?"</string>
+ <string name="autofill_update_title_with_3types" msgid="1312232153076212291">"Желите ли да ажурирате ове ставке у услузи "<b>"<xliff:g id="LABEL">%4$s</xliff:g>"</b>": <xliff:g id="TYPE_0">%1$s</xliff:g>, <xliff:g id="TYPE_1">%2$s</xliff:g> и <xliff:g id="TYPE_2">%3$s</xliff:g>?"</string>
+ <string name="autofill_save_yes" msgid="8035743017382012850">"Сачувај"</string>
+ <string name="autofill_save_no" msgid="9212826374207023544">"Не, хвала"</string>
+ <string name="autofill_save_notnow" msgid="2853932672029024195">"Не сада"</string>
+ <string name="autofill_save_never" msgid="6821841919831402526">"Никада"</string>
+ <string name="autofill_update_yes" msgid="4608662968996874445">"Ажурирај"</string>
+ <string name="autofill_continue_yes" msgid="7914985605534510385">"Настави"</string>
+ <string name="autofill_save_type_password" msgid="5624528786144539944">"лозинка"</string>
+ <string name="autofill_save_type_address" msgid="3111006395818252885">"адреса"</string>
+ <string name="autofill_save_type_credit_card" msgid="3583795235862046693">"кредитна картица"</string>
+ <string name="autofill_save_type_debit_card" msgid="3169397504133097468">"дебитна картица"</string>
+ <string name="autofill_save_type_payment_card" msgid="6555012156728690856">"платна картица"</string>
+ <string name="autofill_save_type_generic_card" msgid="1019367283921448608">"картица"</string>
+ <string name="autofill_save_type_username" msgid="1018816929884640882">"корисничко име"</string>
+ <string name="autofill_save_type_email_address" msgid="1303262336895591924">"имејл адреса"</string>
+ <string name="etws_primary_default_message_earthquake" msgid="8401079517718280669">"Останите мирни и потражите склониште у околини."</string>
+ <string name="etws_primary_default_message_tsunami" msgid="5828171463387976279">"Одмах се склоните из приобалних региона и области поред река на неко безбедније место, на пример, на неко узвишење."</string>
+ <string name="etws_primary_default_message_earthquake_and_tsunami" msgid="4888224011071875068">"Останите мирни и потражите склониште у околини."</string>
+ <string name="etws_primary_default_message_test" msgid="4583367373909549421">"Тестирање порука у хитним случајевима"</string>
+ <string name="notification_reply_button_accessibility" msgid="5235776156579456126">"Одговори"</string>
<string name="etws_primary_default_message_others" msgid="7958161706019130739"></string>
- <string name="mmcc_authentication_reject" msgid="4891965994643876369">"SIM kartica nije prilagođena za glasovne usluge"</string>
- <string name="mmcc_imsi_unknown_in_hlr" msgid="227760698553988751">"SIM kartica nije podešena za glasovne usluge"</string>
- <string name="mmcc_illegal_ms" msgid="7509650265233909445">"SIM kartica nije prilagođena za glasovne usluge"</string>
- <string name="mmcc_illegal_me" msgid="6505557881889904915">"Telefon nije prilagođen za glasovne usluge"</string>
- <string name="mmcc_authentication_reject_msim_template" msgid="4480853038909922153">"SIM <xliff:g id="SIMNUMBER">%d</xliff:g> nije dozvoljen"</string>
- <string name="mmcc_imsi_unknown_in_hlr_msim_template" msgid="3688508325248599657">"SIM <xliff:g id="SIMNUMBER">%d</xliff:g> nije podešen"</string>
- <string name="mmcc_illegal_ms_msim_template" msgid="832644375774599327">"SIM <xliff:g id="SIMNUMBER">%d</xliff:g> nije dozvoljen"</string>
- <string name="mmcc_illegal_me_msim_template" msgid="4802735138861422802">"SIM <xliff:g id="SIMNUMBER">%d</xliff:g> nije dozvoljen"</string>
- <string name="popup_window_default_title" msgid="6907717596694826919">"Iskačući prozor"</string>
- <string name="slice_more_content" msgid="3377367737876888459">"i još <xliff:g id="NUMBER">%1$d</xliff:g>"</string>
- <string name="shortcut_restored_on_lower_version" msgid="9206301954024286063">"Aplikacija je vraćena na stariju verziju ili nije kompatibilna sa ovom prečicom"</string>
- <string name="shortcut_restore_not_supported" msgid="4763198938588468400">"Vraćanje prečice nije uspelo jer aplikacija ne podržava pravljenje rezervne kopije i vraćanje"</string>
- <string name="shortcut_restore_signature_mismatch" msgid="579345304221605479">"Vraćanje prečice nije uspelo jer se potpisi aplikacija ne podudaraju"</string>
- <string name="shortcut_restore_unknown_issue" msgid="2478146134395982154">"Vraćanje prečice nije uspelo"</string>
- <string name="shortcut_disabled_reason_unknown" msgid="753074793553599166">"Prečica je onemogućena"</string>
- <string name="harmful_app_warning_uninstall" msgid="6472912975664191772">"DEINSTALIRAJ"</string>
- <string name="harmful_app_warning_open_anyway" msgid="5963657791740211807">"IPAK OTVORI"</string>
- <string name="harmful_app_warning_title" msgid="8794823880881113856">"Otkrivena je štetna aplikacija"</string>
- <string name="slices_permission_request" msgid="3677129866636153406">"Aplikacija <xliff:g id="APP_0">%1$s</xliff:g> želi da prikazuje isečke iz aplikacije <xliff:g id="APP_2">%2$s</xliff:g>"</string>
- <string name="screenshot_edit" msgid="7408934887203689207">"Izmeni"</string>
- <string name="volume_dialog_ringer_guidance_vibrate" msgid="2055927873175228519">"Vibracija za pozive i obaveštenja je uključena"</string>
- <string name="volume_dialog_ringer_guidance_silent" msgid="1011246774949993783">"Melodija zvona za pozive i obaveštenje je isključena"</string>
- <string name="notification_channel_system_changes" msgid="2462010596920209678">"Sistemske promene"</string>
- <string name="notification_channel_do_not_disturb" msgid="7832584281883687653">"Ne uznemiravaj"</string>
- <string name="zen_upgrade_notification_visd_title" msgid="2001148984371968620">"Novo: Režim Ne uznemiravaj krije obaveštenja"</string>
- <string name="zen_upgrade_notification_visd_content" msgid="3683314609114134946">"Dodirnite da biste saznali više i promenili podešavanje."</string>
- <string name="zen_upgrade_notification_title" msgid="8198167698095298717">"Režim Ne uznemiravaj je promenjen"</string>
- <string name="zen_upgrade_notification_content" msgid="5228458567180124005">"Dodirnite da biste proverili šta je blokirano."</string>
- <string name="review_notification_settings_title" msgid="5102557424459810820">"Pregledajte podešavanja obaveštenja"</string>
- <string name="review_notification_settings_text" msgid="5916244866751849279">"Od Android-a 13 aplikacije koje instalirate moraju da imaju dozvolu za slanje obaveštenja. Dodirnite da biste promenili ovu dozvolu za postojeće aplikacije."</string>
- <string name="review_notification_settings_remind_me_action" msgid="1081081018678480907">"Podseti me kasnije"</string>
- <string name="review_notification_settings_dismiss" msgid="4160916504616428294">"Odbaci"</string>
- <string name="notification_app_name_system" msgid="3045196791746735601">"Sistem"</string>
- <string name="notification_app_name_settings" msgid="9088548800899952531">"Podešavanja"</string>
- <string name="notification_appops_camera_active" msgid="8177643089272352083">"Kamera"</string>
- <string name="notification_appops_microphone_active" msgid="581333393214739332">"Mikrofon"</string>
- <string name="notification_appops_overlay_active" msgid="5571732753262836481">"prikazuje se na ekranu dok koristite druge aplikacije"</string>
- <string name="notification_feedback_indicator" msgid="663476517711323016">"Pošaljite povratne informacije"</string>
- <string name="notification_feedback_indicator_alerted" msgid="6552871804121942099">"Ovo obaveštenje je unapređeno u Podrazumevano. Dodirnite da biste naveli povratne informacije."</string>
- <string name="notification_feedback_indicator_silenced" msgid="3799442124723177262">"Ovo obaveštenje je degradirano u Nečujno. Dodirnite da biste naveli povratne informacije."</string>
- <string name="notification_feedback_indicator_promoted" msgid="9030204303764698640">"Ovo obaveštenje je rangirano više. Dodirnite da biste naveli povratne informacije."</string>
- <string name="notification_feedback_indicator_demoted" msgid="8880309924296450875">"Ovo obaveštenje je rangirano niže. Dodirnite da biste naveli povratne informacije."</string>
- <string name="nas_upgrade_notification_title" msgid="8436359459300146555">"Poboljšana obaveštenja"</string>
- <string name="nas_upgrade_notification_content" msgid="5157550369837103337">"Predložene radnje i odgovore sada dobijate pomoću poboljšanih obaveštenja. Prilagodljiva obaveštenja za Android više nisu podržana."</string>
- <string name="nas_upgrade_notification_enable_action" msgid="3046406808378726874">"Potvrdi"</string>
- <string name="nas_upgrade_notification_disable_action" msgid="3794833210043497982">"Isključi"</string>
- <string name="nas_upgrade_notification_learn_more_action" msgid="7011130656195423947">"Saznajte više"</string>
- <string name="nas_upgrade_notification_learn_more_content" msgid="3735480566983530650">"Poboljšana obaveštenja su zamenila Android prilagodljiva obaveštenja u Android-u 12. Ova funkcija pokazuje predložene radnje i odgovore, i organizuje obaveštenja.\n\nPoboljšana obaveštenja mogu da pristupaju sadržaju obaveštenja, uključujući lične podatke poput imena kontakata i poruka. Ova funkcija može i da odbacuje obaveštenja ili da odgovara na njih, na primer, da se javlja na telefonske pozive i kontroliše režim Ne uznemiravaj."</string>
- <string name="dynamic_mode_notification_channel_name" msgid="2986926422100223328">"Obaveštenje o informacijama Rutinskog režima"</string>
- <string name="dynamic_mode_notification_title" msgid="1388718452788985481">"Ušteda baterije je uključena"</string>
- <string name="dynamic_mode_notification_summary" msgid="1639031262484979689">"Smanjuje se potrošnja baterije da bi se produžilo njeno trajanje"</string>
- <string name="battery_saver_notification_channel_name" msgid="3918243458067916913">"Ušteda baterije"</string>
- <string name="battery_saver_off_notification_title" msgid="7637255960468032515">"Ušteda baterije je isključena"</string>
- <string name="battery_saver_charged_notification_summary" product="default" msgid="5544457317418624367">"Baterija telefona je dovoljno napunjena. Funkcije više nisu ograničene."</string>
- <string name="battery_saver_charged_notification_summary" product="tablet" msgid="4426317048139996888">"Baterija tableta je dovoljno napunjena. Funkcije više nisu ograničene."</string>
- <string name="battery_saver_charged_notification_summary" product="device" msgid="1031562417867646649">"Baterija uređaja je dovoljno napunjena. Funkcije više nisu ograničene."</string>
- <string name="mime_type_folder" msgid="2203536499348787650">"Folder"</string>
- <string name="mime_type_apk" msgid="3168784749499623902">"Android aplikacija"</string>
- <string name="mime_type_generic" msgid="4606589110116560228">"Datoteka"</string>
- <string name="mime_type_generic_ext" msgid="9220220924380909486">"<xliff:g id="EXTENSION">%1$s</xliff:g> datoteka"</string>
- <string name="mime_type_audio" msgid="4933450584432509875">"Audio datoteka"</string>
- <string name="mime_type_audio_ext" msgid="2615491023840514797">"<xliff:g id="EXTENSION">%1$s</xliff:g> audio datoteka"</string>
- <string name="mime_type_video" msgid="7071965726609428150">"Video"</string>
- <string name="mime_type_video_ext" msgid="185438149044230136">"<xliff:g id="EXTENSION">%1$s</xliff:g> video"</string>
- <string name="mime_type_image" msgid="2134307276151645257">"Slika"</string>
- <string name="mime_type_image_ext" msgid="5743552697560999471">"<xliff:g id="EXTENSION">%1$s</xliff:g> slika"</string>
- <string name="mime_type_compressed" msgid="8737300936080662063">"Arhiva"</string>
- <string name="mime_type_compressed_ext" msgid="4775627287994475737">"<xliff:g id="EXTENSION">%1$s</xliff:g> arhiva"</string>
- <string name="mime_type_document" msgid="3737256839487088554">"Dokument"</string>
- <string name="mime_type_document_ext" msgid="2398002765046677311">"<xliff:g id="EXTENSION">%1$s</xliff:g> dokument"</string>
- <string name="mime_type_spreadsheet" msgid="8188407519131275838">"Tabela"</string>
- <string name="mime_type_spreadsheet_ext" msgid="8720173181137254414">"<xliff:g id="EXTENSION">%1$s</xliff:g> tabela"</string>
- <string name="mime_type_presentation" msgid="1145384236788242075">"Prezentacija"</string>
- <string name="mime_type_presentation_ext" msgid="8761049335564371468">"<xliff:g id="EXTENSION">%1$s</xliff:g> prezentacija"</string>
- <string name="bluetooth_airplane_mode_toast" msgid="2066399056595768554">"Bluetooth ostaje uključen tokom režima rada u avionu"</string>
- <string name="car_loading_profile" msgid="8219978381196748070">"Učitava se"</string>
- <string name="file_count" msgid="3220018595056126969">"{count,plural, =1{{file_name} + # fajl}one{{file_name} + # fajl}few{{file_name} + # fajla}other{{file_name} + # fajlova}}"</string>
- <string name="chooser_no_direct_share_targets" msgid="1511722103987329028">"Nema preporučenih ljudi za deljenje"</string>
- <string name="chooser_all_apps_button_label" msgid="3230427756238666328">"Lista aplikacija"</string>
- <string name="usb_device_resolve_prompt_warn" msgid="325871329788064199">"Ova aplikacija nema dozvolu za snimanje, ali bi mogla da snima zvuk pomoću ovog USB uređaja."</string>
- <string name="accessibility_system_action_home_label" msgid="3234748160850301870">"Početak"</string>
- <string name="accessibility_system_action_back_label" msgid="4205361367345537608">"Nazad"</string>
- <string name="accessibility_system_action_recents_label" msgid="4782875610281649728">"Nedavne aplikacije"</string>
- <string name="accessibility_system_action_notifications_label" msgid="6083767351772162010">"Obaveštenja"</string>
- <string name="accessibility_system_action_quick_settings_label" msgid="4583900123506773783">"Brza podešavanja"</string>
- <string name="accessibility_system_action_power_dialog_label" msgid="8095341821683910781">"Dijalog napajanja"</string>
- <string name="accessibility_system_action_lock_screen_label" msgid="5484190691945563838">"Zaključavanje ekrana"</string>
- <string name="accessibility_system_action_screenshot_label" msgid="3581566515062741676">"Snimak ekrana"</string>
- <string name="accessibility_system_action_headset_hook_label" msgid="8524691721287425468">"Kuka za slušalice"</string>
- <string name="accessibility_system_action_on_screen_a11y_shortcut_label" msgid="8488701469459210309">"Prečica za pristupačnost na ekranu"</string>
- <string name="accessibility_system_action_on_screen_a11y_shortcut_chooser_label" msgid="1057878690209817886">"Alatka za biranje prečica za pristupačnost na ekranu"</string>
- <string name="accessibility_system_action_hardware_a11y_shortcut_label" msgid="5764644187715255107">"Prečica za pristupačnost"</string>
- <string name="accessibility_system_action_dismiss_notification_shade" msgid="8931637495533770352">"Odbaci traku sa obaveštenjima"</string>
- <string name="accessibility_system_action_dpad_up_label" msgid="1029042950229333782">"nagore na D-pad-u"</string>
- <string name="accessibility_system_action_dpad_down_label" msgid="3441918448624921461">"nadole na D-pad-u"</string>
- <string name="accessibility_system_action_dpad_left_label" msgid="6557647179116479152">"nalevo na D-pad-u"</string>
- <string name="accessibility_system_action_dpad_right_label" msgid="9180196950365804081">"nadesno na D-pad-u"</string>
- <string name="accessibility_system_action_dpad_center_label" msgid="8149791419358224893">"centar na D-pad-u"</string>
- <string name="accessibility_freeform_caption" msgid="8377519323496290122">"Traka sa naslovima aplikacije <xliff:g id="APP_NAME">%1$s</xliff:g>."</string>
- <string name="as_app_forced_to_restricted_bucket" msgid="8233871289353898964">"Paket <xliff:g id="PACKAGE_NAME">%1$s</xliff:g> je dodat u segment OGRANIČENO"</string>
+ <string name="mmcc_authentication_reject" msgid="4891965994643876369">"SIM картица није прилагођена за гласовне услуге"</string>
+ <string name="mmcc_imsi_unknown_in_hlr" msgid="227760698553988751">"SIM картица није подешена за гласовне услуге"</string>
+ <string name="mmcc_illegal_ms" msgid="7509650265233909445">"SIM картица није прилагођена за гласовне услуге"</string>
+ <string name="mmcc_illegal_me" msgid="6505557881889904915">"Телефон није прилагођен за гласовне услуге"</string>
+ <string name="mmcc_authentication_reject_msim_template" msgid="4480853038909922153">"SIM <xliff:g id="SIMNUMBER">%d</xliff:g> није дозвољен"</string>
+ <string name="mmcc_imsi_unknown_in_hlr_msim_template" msgid="3688508325248599657">"SIM <xliff:g id="SIMNUMBER">%d</xliff:g> није подешен"</string>
+ <string name="mmcc_illegal_ms_msim_template" msgid="832644375774599327">"SIM <xliff:g id="SIMNUMBER">%d</xliff:g> није дозвољен"</string>
+ <string name="mmcc_illegal_me_msim_template" msgid="4802735138861422802">"SIM <xliff:g id="SIMNUMBER">%d</xliff:g> није дозвољен"</string>
+ <string name="popup_window_default_title" msgid="6907717596694826919">"Искачући прозор"</string>
+ <string name="slice_more_content" msgid="3377367737876888459">"и још <xliff:g id="NUMBER">%1$d</xliff:g>"</string>
+ <string name="shortcut_restored_on_lower_version" msgid="9206301954024286063">"Апликација је враћена на старију верзију или није компатибилна са овом пречицом"</string>
+ <string name="shortcut_restore_not_supported" msgid="4763198938588468400">"Враћање пречице није успело јер апликација не подржава прављење резервне копије и враћање"</string>
+ <string name="shortcut_restore_signature_mismatch" msgid="579345304221605479">"Враћање пречице није успело јер се потписи апликација не подударају"</string>
+ <string name="shortcut_restore_unknown_issue" msgid="2478146134395982154">"Враћање пречице није успело"</string>
+ <string name="shortcut_disabled_reason_unknown" msgid="753074793553599166">"Пречица је онемогућена"</string>
+ <string name="harmful_app_warning_uninstall" msgid="6472912975664191772">"ДЕИНСТАЛИРАЈ"</string>
+ <string name="harmful_app_warning_open_anyway" msgid="5963657791740211807">"ИПАК ОТВОРИ"</string>
+ <string name="harmful_app_warning_title" msgid="8794823880881113856">"Откривена је штетна апликација"</string>
+ <string name="slices_permission_request" msgid="3677129866636153406">"Апликација <xliff:g id="APP_0">%1$s</xliff:g> жели да приказује исечке из апликације <xliff:g id="APP_2">%2$s</xliff:g>"</string>
+ <string name="screenshot_edit" msgid="7408934887203689207">"Измени"</string>
+ <string name="volume_dialog_ringer_guidance_vibrate" msgid="2055927873175228519">"Вибрација за позиве и обавештења је укључена"</string>
+ <string name="volume_dialog_ringer_guidance_silent" msgid="1011246774949993783">"Мелодија звона за позиве и обавештење је искључена"</string>
+ <string name="notification_channel_system_changes" msgid="2462010596920209678">"Системске промене"</string>
+ <string name="notification_channel_do_not_disturb" msgid="7832584281883687653">"Не узнемиравај"</string>
+ <string name="zen_upgrade_notification_visd_title" msgid="2001148984371968620">"Ново: Режим Не узнемиравај крије обавештења"</string>
+ <string name="zen_upgrade_notification_visd_content" msgid="3683314609114134946">"Додирните да бисте сазнали више и променили подешавање."</string>
+ <string name="zen_upgrade_notification_title" msgid="8198167698095298717">"Режим Не узнемиравај је промењен"</string>
+ <string name="zen_upgrade_notification_content" msgid="5228458567180124005">"Додирните да бисте проверили шта је блокирано."</string>
+ <string name="review_notification_settings_title" msgid="5102557424459810820">"Прегледајте подешавања обавештења"</string>
+ <string name="review_notification_settings_text" msgid="5916244866751849279">"Од Android-а 13 апликације које инсталирате морају да имају дозволу за слање обавештења. Додирните да бисте променили ову дозволу за постојеће апликације."</string>
+ <string name="review_notification_settings_remind_me_action" msgid="1081081018678480907">"Подсети ме касније"</string>
+ <string name="review_notification_settings_dismiss" msgid="4160916504616428294">"Одбаци"</string>
+ <string name="notification_app_name_system" msgid="3045196791746735601">"Систем"</string>
+ <string name="notification_app_name_settings" msgid="9088548800899952531">"Подешавања"</string>
+ <string name="notification_appops_camera_active" msgid="8177643089272352083">"Камера"</string>
+ <string name="notification_appops_microphone_active" msgid="581333393214739332">"Микрофон"</string>
+ <string name="notification_appops_overlay_active" msgid="5571732753262836481">"приказује се на екрану док користите друге апликације"</string>
+ <string name="notification_feedback_indicator" msgid="663476517711323016">"Пошаљите повратне информације"</string>
+ <string name="notification_feedback_indicator_alerted" msgid="6552871804121942099">"Ово обавештење је унапређено у Подразумевано. Додирните да бисте навели повратне информације."</string>
+ <string name="notification_feedback_indicator_silenced" msgid="3799442124723177262">"Ово обавештење је деградирано у Нечујно. Додирните да бисте навели повратне информације."</string>
+ <string name="notification_feedback_indicator_promoted" msgid="9030204303764698640">"Ово обавештење је рангирано више. Додирните да бисте навели повратне информације."</string>
+ <string name="notification_feedback_indicator_demoted" msgid="8880309924296450875">"Ово обавештење је рангирано ниже. Додирните да бисте навели повратне информације."</string>
+ <string name="nas_upgrade_notification_title" msgid="8436359459300146555">"Побољшана обавештења"</string>
+ <string name="nas_upgrade_notification_content" msgid="5157550369837103337">"Предложене радње и одговоре сада добијате помоћу побољшаних обавештења. Прилагодљива обавештења за Android више нису подржана."</string>
+ <string name="nas_upgrade_notification_enable_action" msgid="3046406808378726874">"Потврди"</string>
+ <string name="nas_upgrade_notification_disable_action" msgid="3794833210043497982">"Искључи"</string>
+ <string name="nas_upgrade_notification_learn_more_action" msgid="7011130656195423947">"Сазнајте више"</string>
+ <string name="nas_upgrade_notification_learn_more_content" msgid="3735480566983530650">"Побољшана обавештења су заменила Android прилагодљива обавештења у Android-у 12. Ова функција показује предложене радње и одговоре, и организује обавештења.\n\nПобољшана обавештења могу да приступају садржају обавештења, укључујући личне податке попут имена контаката и порука. Ова функција може и да одбацује обавештења или да одговара на њих, на пример, да се јавља на телефонске позиве и контролише режим Не узнемиравај."</string>
+ <string name="dynamic_mode_notification_channel_name" msgid="2986926422100223328">"Обавештење о информацијама Рутинског режима"</string>
+ <string name="dynamic_mode_notification_title" msgid="1388718452788985481">"Уштеда батерије је укључена"</string>
+ <string name="dynamic_mode_notification_summary" msgid="1639031262484979689">"Смањује се потрошња батерије да би се продужило њено трајање"</string>
+ <string name="battery_saver_notification_channel_name" msgid="3918243458067916913">"Уштеда батерије"</string>
+ <string name="battery_saver_off_notification_title" msgid="7637255960468032515">"Уштеда батерије је искључена"</string>
+ <string name="battery_saver_charged_notification_summary" product="default" msgid="5544457317418624367">"Батерија телефона је довољно напуњена. Функције више нису ограничене."</string>
+ <string name="battery_saver_charged_notification_summary" product="tablet" msgid="4426317048139996888">"Батерија таблета је довољно напуњена. Функције више нису ограничене."</string>
+ <string name="battery_saver_charged_notification_summary" product="device" msgid="1031562417867646649">"Батерија уређаја је довољно напуњена. Функције више нису ограничене."</string>
+ <string name="mime_type_folder" msgid="2203536499348787650">"Фолдер"</string>
+ <string name="mime_type_apk" msgid="3168784749499623902">"Android апликација"</string>
+ <string name="mime_type_generic" msgid="4606589110116560228">"Датотека"</string>
+ <string name="mime_type_generic_ext" msgid="9220220924380909486">"<xliff:g id="EXTENSION">%1$s</xliff:g> датотека"</string>
+ <string name="mime_type_audio" msgid="4933450584432509875">"Аудио датотека"</string>
+ <string name="mime_type_audio_ext" msgid="2615491023840514797">"<xliff:g id="EXTENSION">%1$s</xliff:g> аудио датотека"</string>
+ <string name="mime_type_video" msgid="7071965726609428150">"Видео"</string>
+ <string name="mime_type_video_ext" msgid="185438149044230136">"<xliff:g id="EXTENSION">%1$s</xliff:g> видео"</string>
+ <string name="mime_type_image" msgid="2134307276151645257">"Слика"</string>
+ <string name="mime_type_image_ext" msgid="5743552697560999471">"<xliff:g id="EXTENSION">%1$s</xliff:g> слика"</string>
+ <string name="mime_type_compressed" msgid="8737300936080662063">"Архива"</string>
+ <string name="mime_type_compressed_ext" msgid="4775627287994475737">"<xliff:g id="EXTENSION">%1$s</xliff:g> архива"</string>
+ <string name="mime_type_document" msgid="3737256839487088554">"Документ"</string>
+ <string name="mime_type_document_ext" msgid="2398002765046677311">"<xliff:g id="EXTENSION">%1$s</xliff:g> документ"</string>
+ <string name="mime_type_spreadsheet" msgid="8188407519131275838">"Табела"</string>
+ <string name="mime_type_spreadsheet_ext" msgid="8720173181137254414">"<xliff:g id="EXTENSION">%1$s</xliff:g> табела"</string>
+ <string name="mime_type_presentation" msgid="1145384236788242075">"Презентација"</string>
+ <string name="mime_type_presentation_ext" msgid="8761049335564371468">"<xliff:g id="EXTENSION">%1$s</xliff:g> презентација"</string>
+ <string name="bluetooth_airplane_mode_toast" msgid="2066399056595768554">"Bluetooth остаје укључен током режима рада у авиону"</string>
+ <string name="car_loading_profile" msgid="8219978381196748070">"Учитава се"</string>
+ <string name="file_count" msgid="3220018595056126969">"{count,plural, =1{{file_name} + # фајл}one{{file_name} + # фајл}few{{file_name} + # фајла}other{{file_name} + # фајлова}}"</string>
+ <string name="chooser_no_direct_share_targets" msgid="1511722103987329028">"Нема препоручених људи за дељење"</string>
+ <string name="chooser_all_apps_button_label" msgid="3230427756238666328">"Листа апликација"</string>
+ <string name="usb_device_resolve_prompt_warn" msgid="325871329788064199">"Ова апликација нема дозволу за снимање, али би могла да снима звук помоћу овог USB уређаја."</string>
+ <string name="accessibility_system_action_home_label" msgid="3234748160850301870">"Почетак"</string>
+ <string name="accessibility_system_action_back_label" msgid="4205361367345537608">"Назад"</string>
+ <string name="accessibility_system_action_recents_label" msgid="4782875610281649728">"Недавне апликације"</string>
+ <string name="accessibility_system_action_notifications_label" msgid="6083767351772162010">"Обавештења"</string>
+ <string name="accessibility_system_action_quick_settings_label" msgid="4583900123506773783">"Брза подешавања"</string>
+ <string name="accessibility_system_action_power_dialog_label" msgid="8095341821683910781">"Дијалог напајања"</string>
+ <string name="accessibility_system_action_lock_screen_label" msgid="5484190691945563838">"Закључавање екрана"</string>
+ <string name="accessibility_system_action_screenshot_label" msgid="3581566515062741676">"Снимак екрана"</string>
+ <string name="accessibility_system_action_headset_hook_label" msgid="8524691721287425468">"Кука за слушалице"</string>
+ <string name="accessibility_system_action_on_screen_a11y_shortcut_label" msgid="8488701469459210309">"Пречица за приступачност на екрану"</string>
+ <string name="accessibility_system_action_on_screen_a11y_shortcut_chooser_label" msgid="1057878690209817886">"Алатка за бирање пречица за приступачност на екрану"</string>
+ <string name="accessibility_system_action_hardware_a11y_shortcut_label" msgid="5764644187715255107">"Пречица за приступачност"</string>
+ <string name="accessibility_system_action_dismiss_notification_shade" msgid="8931637495533770352">"Одбаци траку са обавештењима"</string>
+ <string name="accessibility_system_action_dpad_up_label" msgid="1029042950229333782">"нагоре на D-pad-у"</string>
+ <string name="accessibility_system_action_dpad_down_label" msgid="3441918448624921461">"надоле на D-pad-у"</string>
+ <string name="accessibility_system_action_dpad_left_label" msgid="6557647179116479152">"налево на D-pad-у"</string>
+ <string name="accessibility_system_action_dpad_right_label" msgid="9180196950365804081">"надесно на D-pad-у"</string>
+ <string name="accessibility_system_action_dpad_center_label" msgid="8149791419358224893">"центар на D-pad-у"</string>
+ <string name="accessibility_freeform_caption" msgid="8377519323496290122">"Трака са насловима апликације <xliff:g id="APP_NAME">%1$s</xliff:g>."</string>
+ <string name="as_app_forced_to_restricted_bucket" msgid="8233871289353898964">"Пакет <xliff:g id="PACKAGE_NAME">%1$s</xliff:g> је додат у сегмент ОГРАНИЧЕНО"</string>
<string name="conversation_single_line_name_display" msgid="8958948312915255999">"<xliff:g id="SENDER_NAME">%1$s</xliff:g>:"</string>
- <string name="conversation_single_line_image_placeholder" msgid="6983271082911936900">"je poslao/la sliku"</string>
- <string name="conversation_title_fallback_one_to_one" msgid="1980753619726908614">"Konverzacija"</string>
- <string name="conversation_title_fallback_group_chat" msgid="456073374993104303">"Grupna konverzacija"</string>
+ <string name="conversation_single_line_image_placeholder" msgid="6983271082911936900">"је послао/ла слику"</string>
+ <string name="conversation_title_fallback_one_to_one" msgid="1980753619726908614">"Конверзација"</string>
+ <string name="conversation_title_fallback_group_chat" msgid="456073374993104303">"Групна конверзација"</string>
<string name="unread_convo_overflow" msgid="920517615597353833">"<xliff:g id="MAX_UNREAD_COUNT">%1$d</xliff:g>+"</string>
- <string name="resolver_personal_tab" msgid="2051260504014442073">"Lično"</string>
- <string name="resolver_work_tab" msgid="2690019516263167035">"Poslovno"</string>
- <string name="resolver_personal_tab_accessibility" msgid="5739524949153091224">"Lični prikaz"</string>
- <string name="resolver_work_tab_accessibility" msgid="4753168230363802734">"Prikaz za posao"</string>
- <string name="resolver_cross_profile_blocked" msgid="3014597376026044840">"Blokira IT administrator"</string>
- <string name="resolver_cant_share_with_work_apps_explanation" msgid="9071442683080586643">"Ovaj sadržaj ne može da se deli pomoću poslovnih aplikacija"</string>
- <string name="resolver_cant_access_work_apps_explanation" msgid="1129960195389373279">"Ovaj sadržaj ne može da se otvara pomoću poslovnih aplikacija"</string>
- <string name="resolver_cant_share_with_personal_apps_explanation" msgid="6349766201904601544">"Ovaj sadržaj ne može da se deli pomoću ličnih aplikacija"</string>
- <string name="resolver_cant_access_personal_apps_explanation" msgid="1679399548862724359">"Ovaj sadržaj ne može da se otvara pomoću ličnih aplikacija"</string>
- <string name="resolver_turn_on_work_apps" msgid="884910835250037247">"Poslovni profil je pauziran"</string>
- <string name="resolver_switch_on_work" msgid="463709043650610420">"Dodirnite da biste uključili"</string>
- <string name="resolver_no_work_apps_available" msgid="3298291360133337270">"Nema poslovnih aplikacija"</string>
- <string name="resolver_no_personal_apps_available" msgid="6284837227019594881">"Nema ličnih aplikacija"</string>
- <string name="miniresolver_open_in_personal" msgid="3874522693661065566">"Želite da na ličnom profilu otvorite: <xliff:g id="APP">%s</xliff:g>?"</string>
- <string name="miniresolver_open_in_work" msgid="4415223793669536559">"Želite da na poslovnom profilu otvorite: <xliff:g id="APP">%s</xliff:g>?"</string>
- <string name="miniresolver_use_personal_browser" msgid="776072682871133308">"Koristi lični pregledač"</string>
- <string name="miniresolver_use_work_browser" msgid="543575306251952994">"Koristi poslovni pregledač"</string>
- <string name="PERSOSUBSTATE_SIM_NETWORK_ENTRY" msgid="8050953231914637819">"PIN za otključavanje SIM mreže"</string>
- <string name="PERSOSUBSTATE_SIM_NETWORK_SUBSET_ENTRY" msgid="7164399703751688214">"PIN za otključavanje podskupa SIM mreže"</string>
- <string name="PERSOSUBSTATE_SIM_CORPORATE_ENTRY" msgid="4447629474818217364">"PIN za otključavanje poslovne SIM kartice"</string>
- <string name="PERSOSUBSTATE_SIM_SERVICE_PROVIDER_ENTRY" msgid="973059024670737358">"PIN za otključavanje dobavljača usluge SIM kartice"</string>
- <string name="PERSOSUBSTATE_SIM_SIM_ENTRY" msgid="4487435301206073787">"PIN za otključavanje SIM kartice"</string>
- <string name="PERSOSUBSTATE_SIM_NETWORK_PUK_ENTRY" msgid="768060297218652809">"Unesite PUK"</string>
- <string name="PERSOSUBSTATE_SIM_NETWORK_SUBSET_PUK_ENTRY" msgid="7129527319490548930">"Unesite PUK"</string>
- <string name="PERSOSUBSTATE_SIM_CORPORATE_PUK_ENTRY" msgid="2876126640607573252">"Unesite PUK"</string>
- <string name="PERSOSUBSTATE_SIM_SERVICE_PROVIDER_PUK_ENTRY" msgid="8952595089930109282">"Unesite PUK"</string>
- <string name="PERSOSUBSTATE_SIM_SIM_PUK_ENTRY" msgid="3013902515773728996">"Unesite PUK"</string>
- <string name="PERSOSUBSTATE_RUIM_NETWORK1_ENTRY" msgid="2974411408893410289">"PIN za otključavanje RUIM mreže 1"</string>
- <string name="PERSOSUBSTATE_RUIM_NETWORK2_ENTRY" msgid="687618528751880721">"PIN za otključavanje RUIM mreže 2"</string>
- <string name="PERSOSUBSTATE_RUIM_HRPD_ENTRY" msgid="6810596579655575381">"PIN za otključavanje RUIM hrpd-a"</string>
- <string name="PERSOSUBSTATE_RUIM_CORPORATE_ENTRY" msgid="2715929642540980259">"PIN za otključavanje poslovne RUIM kartice"</string>
- <string name="PERSOSUBSTATE_RUIM_SERVICE_PROVIDER_ENTRY" msgid="8557791623303951590">"PIN za otključavanje RUIM kartice dobavljača usluge"</string>
- <string name="PERSOSUBSTATE_RUIM_RUIM_ENTRY" msgid="7382468767274580323">"PIN za otključavanje RUIM kartice"</string>
- <string name="PERSOSUBSTATE_RUIM_NETWORK1_PUK_ENTRY" msgid="6730880791104286987">"Unesite PUK"</string>
- <string name="PERSOSUBSTATE_RUIM_NETWORK2_PUK_ENTRY" msgid="6432126539782267026">"Unesite PUK"</string>
- <string name="PERSOSUBSTATE_RUIM_HRPD_PUK_ENTRY" msgid="1730510161529488920">"Unesite PUK"</string>
- <string name="PERSOSUBSTATE_RUIM_SERVICE_PROVIDER_PUK_ENTRY" msgid="3369885925003346830">"Unesite PUK"</string>
- <string name="PERSOSUBSTATE_RUIM_RUIM_PUK_ENTRY" msgid="9129139686191167829">"Unesite PUK"</string>
- <string name="PERSOSUBSTATE_RUIM_CORPORATE_PUK_ENTRY" msgid="2869929685874615358">"Unesite PUK"</string>
- <string name="PERSOSUBSTATE_SIM_SPN_ENTRY" msgid="1238663472392741771">"PIN za otključavanje SPN-a"</string>
- <string name="PERSOSUBSTATE_SIM_SP_EHPLMN_ENTRY" msgid="3988705848553894358">"PIN za otključavanje SP ekvivalentnog matičnog PLMN-a"</string>
- <string name="PERSOSUBSTATE_SIM_ICCID_ENTRY" msgid="6186770686690993200">"PIN za otključavanje ICCID-a"</string>
- <string name="PERSOSUBSTATE_SIM_IMPI_ENTRY" msgid="7043865376145617024">"PIN za otključavanje IMPI-ja"</string>
- <string name="PERSOSUBSTATE_SIM_NS_SP_ENTRY" msgid="6144227308185112176">"PIN za otključavanje dobavljača usluge podskupa mreže"</string>
- <string name="PERSOSUBSTATE_SIM_NETWORK_IN_PROGRESS" msgid="4233355366318061180">"Zahteva se otključavanje SIM mreže…"</string>
- <string name="PERSOSUBSTATE_SIM_NETWORK_SUBSET_IN_PROGRESS" msgid="6742563947637715645">"Zahteva se otključavanje podskupa SIM mreže…"</string>
- <string name="PERSOSUBSTATE_SIM_SERVICE_PROVIDER_IN_PROGRESS" msgid="2033399698172403560">"Zahteva se otključavanje dobavljača usluge SIM kartice…"</string>
- <string name="PERSOSUBSTATE_SIM_CORPORATE_IN_PROGRESS" msgid="4795977251920732254">"Zahteva se otključavanje poslovne SIM kartice…"</string>
- <string name="PERSOSUBSTATE_SIM_NETWORK_PUK_IN_PROGRESS" msgid="1090425878157254446">"Zahteva se otključavanje pomoću PUK-a…"</string>
- <string name="PERSOSUBSTATE_SIM_NETWORK_SUBSET_PUK_IN_PROGRESS" msgid="6476898876518094438">"Zahteva se otključavanje pomoću PUK-a…"</string>
- <string name="PERSOSUBSTATE_SIM_CORPORATE_PUK_IN_PROGRESS" msgid="6006806734293747731">"Zahteva se otključavanje pomoću PUK-a…"</string>
- <string name="PERSOSUBSTATE_SIM_SERVICE_PROVIDER_PUK_IN_PROGRESS" msgid="6546680489620881893">"Zahteva se otključavanje pomoću PUK-a…"</string>
- <string name="PERSOSUBSTATE_SIM_SIM_PUK_IN_PROGRESS" msgid="3506845511000727015">"Zahteva se otključavanje pomoću PUK-a…"</string>
- <string name="PERSOSUBSTATE_SIM_SIM_IN_PROGRESS" msgid="6709169861932992750">"Zahteva se otključavanje SIM kartice…"</string>
- <string name="PERSOSUBSTATE_RUIM_NETWORK1_IN_PROGRESS" msgid="4013870911606478520">"Zahteva se otključavanje RUIM mreže 1…"</string>
- <string name="PERSOSUBSTATE_RUIM_NETWORK2_IN_PROGRESS" msgid="9032651188219523434">"Zahteva se otključavanje RUIM mreže 2…"</string>
- <string name="PERSOSUBSTATE_RUIM_HRPD_IN_PROGRESS" msgid="6584576506344491207">"Zahteva se otključavanje RUIM hrpd-a…"</string>
- <string name="PERSOSUBSTATE_RUIM_SERVICE_PROVIDER_IN_PROGRESS" msgid="830981927724888114">"Zahteva se otključavanje RUIM kartice dobavljača usluge…"</string>
- <string name="PERSOSUBSTATE_RUIM_CORPORATE_IN_PROGRESS" msgid="7851790973098894802">"Zahteva se otključavanje poslovne RUIM kartice…"</string>
- <string name="PERSOSUBSTATE_SIM_SPN_IN_PROGRESS" msgid="1149560739586960121">"Zahteva se otključavanje SPN-a…"</string>
- <string name="PERSOSUBSTATE_SIM_SP_EHPLMN_IN_PROGRESS" msgid="5708964693522116025">"Zahteva se otključavanje SP ekvivalentnog matičnog PLMN-a…"</string>
- <string name="PERSOSUBSTATE_SIM_ICCID_IN_PROGRESS" msgid="7288103122966483455">"Zahteva se otključavanje ICCID-a…"</string>
- <string name="PERSOSUBSTATE_SIM_IMPI_IN_PROGRESS" msgid="4036752174056147753">"Zahteva se otključavanje IMPI-ja…"</string>
- <string name="PERSOSUBSTATE_SIM_NS_SP_IN_PROGRESS" msgid="5089536274515338566">"Zahteva se otključavanje dobavljača usluge podskupa mreže…"</string>
- <string name="PERSOSUBSTATE_RUIM_RUIM_IN_PROGRESS" msgid="6737197986936251958">"Zahteva se otključavanje RUIM kartice…"</string>
- <string name="PERSOSUBSTATE_RUIM_NETWORK1_PUK_IN_PROGRESS" msgid="5658767775619998623">"Zahteva se otključavanje pomoću PUK-a…"</string>
- <string name="PERSOSUBSTATE_RUIM_NETWORK2_PUK_IN_PROGRESS" msgid="665978313257653727">"Zahteva se otključavanje pomoću PUK-a…"</string>
- <string name="PERSOSUBSTATE_RUIM_HRPD_PUK_IN_PROGRESS" msgid="3857142652251836850">"Zahteva se otključavanje pomoću PUK-a…"</string>
- <string name="PERSOSUBSTATE_RUIM_CORPORATE_PUK_IN_PROGRESS" msgid="2695664012344346788">"Zahteva se otključavanje pomoću PUK-a…"</string>
- <string name="PERSOSUBSTATE_RUIM_SERVICE_PROVIDER_PUK_IN_PROGRESS" msgid="2695678959963807782">"Zahteva se otključavanje pomoću PUK-a…"</string>
- <string name="PERSOSUBSTATE_RUIM_RUIM_PUK_IN_PROGRESS" msgid="1230605365926493599">"Zahteva se otključavanje pomoću PUK-a…"</string>
- <string name="PERSOSUBSTATE_SIM_NETWORK_ERROR" msgid="1924844017037151535">"Zahtev za otključavanje SIM mreže nije uspeo."</string>
- <string name="PERSOSUBSTATE_SIM_NETWORK_SUBSET_ERROR" msgid="3372797822292089708">"Zahtev za otključavanje podskupa SIM mreže je uspeo."</string>
- <string name="PERSOSUBSTATE_SIM_SERVICE_PROVIDER_ERROR" msgid="1878443146720411381">"Zahtev za otključavanje dobavljača usluge SIM kartice nije uspeo."</string>
- <string name="PERSOSUBSTATE_SIM_CORPORATE_ERROR" msgid="7664778312218023192">"Zahtev za otključavanje poslovne SIM kartice nije uspeo."</string>
- <string name="PERSOSUBSTATE_SIM_SIM_ERROR" msgid="2472944311643350302">"Zahtev za otključavanje SIM kartice nije uspeo."</string>
- <string name="PERSOSUBSTATE_RUIM_NETWORK1_ERROR" msgid="828089694480999120">"Zahtev za otključavanje RUIM mreže 1 nije uspeo."</string>
- <string name="PERSOSUBSTATE_RUIM_NETWORK2_ERROR" msgid="17619001007092511">"Zahtev za otključavanje RUIM mreže 2 nije uspeo."</string>
- <string name="PERSOSUBSTATE_RUIM_HRPD_ERROR" msgid="807214229604353614">"Zahtev za otključavanje RUIM hrpd-a nije uspeo."</string>
- <string name="PERSOSUBSTATE_RUIM_CORPORATE_ERROR" msgid="8644184447744175747">"Zahtev za otključavanje poslovne RUIM kartice nije uspeo."</string>
- <string name="PERSOSUBSTATE_RUIM_SERVICE_PROVIDER_ERROR" msgid="3801002648649640407">"Zahtev za otključavanje RUIM kartice dobavljača usluge nije uspeo."</string>
- <string name="PERSOSUBSTATE_RUIM_RUIM_ERROR" msgid="707397021218680753">"Zahtev za otključavanje RUIM kartice nije uspeo."</string>
- <string name="PERSOSUBSTATE_SIM_NETWORK_PUK_ERROR" msgid="894358680773257820">"Otključavanje pomoću PUK-a nije uspelo."</string>
- <string name="PERSOSUBSTATE_SIM_NETWORK_SUBSET_PUK_ERROR" msgid="352466878146726991">"Otključavanje pomoću PUK-a nije uspelo."</string>
- <string name="PERSOSUBSTATE_SIM_CORPORATE_PUK_ERROR" msgid="7353389721907138671">"Otključavanje pomoću PUK-a nije uspelo."</string>
- <string name="PERSOSUBSTATE_SIM_SERVICE_PROVIDER_PUK_ERROR" msgid="2655263155490857920">"Otključavanje pomoću PUK-a nije uspelo."</string>
- <string name="PERSOSUBSTATE_SIM_SIM_PUK_ERROR" msgid="6903740900892931310">"Otključavanje pomoću PUK-a nije uspelo."</string>
- <string name="PERSOSUBSTATE_RUIM_NETWORK1_PUK_ERROR" msgid="5165901670447518687">"Otključavanje pomoću PUK-a nije uspelo."</string>
- <string name="PERSOSUBSTATE_RUIM_NETWORK2_PUK_ERROR" msgid="2856763216589267623">"Otključavanje pomoću PUK-a nije uspelo."</string>
- <string name="PERSOSUBSTATE_RUIM_HRPD_PUK_ERROR" msgid="817542684437829139">"Otključavanje pomoću PUK-a nije uspelo."</string>
- <string name="PERSOSUBSTATE_RUIM_SERVICE_PROVIDER_PUK_ERROR" msgid="5178635064113393143">"Otključavanje pomoću PUK-a nije uspelo."</string>
- <string name="PERSOSUBSTATE_RUIM_RUIM_PUK_ERROR" msgid="5391587926974531008">"Otključavanje pomoću PUK-a nije uspelo."</string>
- <string name="PERSOSUBSTATE_RUIM_CORPORATE_PUK_ERROR" msgid="4895494864493315868">"Otključavanje pomoću PUK-a nije uspelo."</string>
- <string name="PERSOSUBSTATE_SIM_SPN_ERROR" msgid="9017576601595353649">"Zahtev za otključavanje SPN-a nije uspeo."</string>
- <string name="PERSOSUBSTATE_SIM_SP_EHPLMN_ERROR" msgid="1116993930995545742">"Zahtev za otključavanje SP ekvivalentnog matičnog PLMN-a nije uspeo."</string>
- <string name="PERSOSUBSTATE_SIM_ICCID_ERROR" msgid="7559167306794441462">"Zahtev za otključavanje ICCID-a nije uspeo."</string>
- <string name="PERSOSUBSTATE_SIM_IMPI_ERROR" msgid="2782926139511136588">"Zahtev za otključavanje IMPI-ja nije uspeo."</string>
- <string name="PERSOSUBSTATE_SIM_NS_SP_ERROR" msgid="1890493954453456758">"Zahtev za otključavanje dobavljača usluge podskupa mreže nije uspeo."</string>
- <string name="PERSOSUBSTATE_SIM_NETWORK_SUCCESS" msgid="4886243367747126325">"Otključavanje SIM mreže je uspelo."</string>
- <string name="PERSOSUBSTATE_SIM_NETWORK_SUBSET_SUCCESS" msgid="4053809277733513987">"Otključavanje podskupa SIM mreže je uspelo."</string>
- <string name="PERSOSUBSTATE_SIM_SERVICE_PROVIDER_SUCCESS" msgid="8249342930499801740">"Otključavanje dobavljača usluge SIM kartice je uspelo."</string>
- <string name="PERSOSUBSTATE_SIM_CORPORATE_SUCCESS" msgid="2339794542560381270">"Otključavanje poslovne SIM kartice je uspelo."</string>
- <string name="PERSOSUBSTATE_SIM_SIM_SUCCESS" msgid="6975608174152828954">"Otključavanje SIM kartice je uspelo."</string>
- <string name="PERSOSUBSTATE_RUIM_NETWORK1_SUCCESS" msgid="2846699261330463192">"Otključavanje RUIM mreže 1 je uspelo."</string>
- <string name="PERSOSUBSTATE_RUIM_NETWORK2_SUCCESS" msgid="5335414726057102801">"Otključavanje RUIM mreže 2 je uspelo."</string>
- <string name="PERSOSUBSTATE_RUIM_HRPD_SUCCESS" msgid="8868100318474971969">"Otključavanje RUIM hrpd-a je uspelo."</string>
- <string name="PERSOSUBSTATE_RUIM_SERVICE_PROVIDER_SUCCESS" msgid="6020936629725666932">"Zahtev za otključavanje RUIM kartice dobavljača usluge je uspeo."</string>
- <string name="PERSOSUBSTATE_RUIM_CORPORATE_SUCCESS" msgid="6944873647584595489">"Otključavanje poslovne RUIM kartice je uspelo."</string>
- <string name="PERSOSUBSTATE_RUIM_RUIM_SUCCESS" msgid="2526483514124121988">"Otključavanje RUIM kartice je uspelo."</string>
- <string name="PERSOSUBSTATE_SIM_NETWORK_PUK_SUCCESS" msgid="7662200333621664621">"Otključavanje pomoću PUK-a je uspelo."</string>
- <string name="PERSOSUBSTATE_SIM_NETWORK_SUBSET_PUK_SUCCESS" msgid="2861223407953766632">"Otključavanje pomoću PUK-a je uspelo."</string>
- <string name="PERSOSUBSTATE_SIM_CORPORATE_PUK_SUCCESS" msgid="5345648571175243272">"Otključavanje pomoću PUK-a je uspelo."</string>
- <string name="PERSOSUBSTATE_SIM_SERVICE_PROVIDER_PUK_SUCCESS" msgid="3725278343103422466">"Otključavanje pomoću PUK-a je uspelo."</string>
- <string name="PERSOSUBSTATE_SIM_SIM_PUK_SUCCESS" msgid="6998502547560297983">"Otključavanje pomoću PUK-a je uspelo."</string>
- <string name="PERSOSUBSTATE_RUIM_NETWORK1_PUK_SUCCESS" msgid="8555433771162560361">"Otključavanje pomoću PUK-a je uspelo."</string>
- <string name="PERSOSUBSTATE_RUIM_NETWORK2_PUK_SUCCESS" msgid="3555767296933606232">"Otključavanje pomoću PUK-a je uspelo."</string>
- <string name="PERSOSUBSTATE_RUIM_HRPD_PUK_SUCCESS" msgid="6778051818199974237">"Otključavanje pomoću PUK-a je uspelo."</string>
- <string name="PERSOSUBSTATE_RUIM_CORPORATE_PUK_SUCCESS" msgid="4080108758498911429">"Otključavanje pomoću PUK-a je uspelo."</string>
- <string name="PERSOSUBSTATE_RUIM_SERVICE_PROVIDER_PUK_SUCCESS" msgid="7873675303000794343">"Otključavanje pomoću PUK-a je uspelo."</string>
- <string name="PERSOSUBSTATE_RUIM_RUIM_PUK_SUCCESS" msgid="1763198215069819523">"Otključavanje pomoću PUK-a je uspelo."</string>
- <string name="PERSOSUBSTATE_SIM_SPN_SUCCESS" msgid="2053891977727320532">"Otključavanje SPN-a je uspelo."</string>
- <string name="PERSOSUBSTATE_SIM_SP_EHPLMN_SUCCESS" msgid="8146602361895007345">"Otključavanje SP ekvivalentnog matičnog PLMN-a je uspelo."</string>
- <string name="PERSOSUBSTATE_SIM_ICCID_SUCCESS" msgid="8058678548991999545">"Otključavanje ICCID-a je uspelo."</string>
- <string name="PERSOSUBSTATE_SIM_IMPI_SUCCESS" msgid="2545608067978550571">"Otključavanje IMPI-ja je uspelo."</string>
- <string name="PERSOSUBSTATE_SIM_NS_SP_SUCCESS" msgid="4352382949744625007">"Zahtev za otključavanje dobavljača usluge podskupa mreže je uspeo."</string>
+ <string name="resolver_personal_tab" msgid="2051260504014442073">"Лично"</string>
+ <string name="resolver_work_tab" msgid="2690019516263167035">"Пословно"</string>
+ <string name="resolver_personal_tab_accessibility" msgid="5739524949153091224">"Лични приказ"</string>
+ <string name="resolver_work_tab_accessibility" msgid="4753168230363802734">"Приказ за посао"</string>
+ <string name="resolver_cross_profile_blocked" msgid="3014597376026044840">"Блокира ИТ администратор"</string>
+ <string name="resolver_cant_share_with_work_apps_explanation" msgid="9071442683080586643">"Овај садржај не може да се дели помоћу пословних апликација"</string>
+ <string name="resolver_cant_access_work_apps_explanation" msgid="1129960195389373279">"Овај садржај не може да се отвара помоћу пословних апликација"</string>
+ <string name="resolver_cant_share_with_personal_apps_explanation" msgid="6349766201904601544">"Овај садржај не може да се дели помоћу личних апликација"</string>
+ <string name="resolver_cant_access_personal_apps_explanation" msgid="1679399548862724359">"Овај садржај не може да се отвара помоћу личних апликација"</string>
+ <string name="resolver_turn_on_work_apps" msgid="884910835250037247">"Пословни профил је паузиран"</string>
+ <string name="resolver_switch_on_work" msgid="463709043650610420">"Додирните да бисте укључили"</string>
+ <string name="resolver_no_work_apps_available" msgid="3298291360133337270">"Нема пословних апликација"</string>
+ <string name="resolver_no_personal_apps_available" msgid="6284837227019594881">"Нема личних апликација"</string>
+ <string name="miniresolver_open_in_personal" msgid="3874522693661065566">"Желите да на личном профилу отворите: <xliff:g id="APP">%s</xliff:g>?"</string>
+ <string name="miniresolver_open_in_work" msgid="4415223793669536559">"Желите да на пословном профилу отворите: <xliff:g id="APP">%s</xliff:g>?"</string>
+ <string name="miniresolver_use_personal_browser" msgid="776072682871133308">"Користи лични прегледач"</string>
+ <string name="miniresolver_use_work_browser" msgid="543575306251952994">"Користи пословни прегледач"</string>
+ <string name="PERSOSUBSTATE_SIM_NETWORK_ENTRY" msgid="8050953231914637819">"PIN за откључавање SIM мреже"</string>
+ <string name="PERSOSUBSTATE_SIM_NETWORK_SUBSET_ENTRY" msgid="7164399703751688214">"PIN за откључавање подскупа SIM мреже"</string>
+ <string name="PERSOSUBSTATE_SIM_CORPORATE_ENTRY" msgid="4447629474818217364">"PIN за откључавање пословне SIM картице"</string>
+ <string name="PERSOSUBSTATE_SIM_SERVICE_PROVIDER_ENTRY" msgid="973059024670737358">"PIN за откључавање добављача услуге SIM картице"</string>
+ <string name="PERSOSUBSTATE_SIM_SIM_ENTRY" msgid="4487435301206073787">"PIN за откључавање SIM картице"</string>
+ <string name="PERSOSUBSTATE_SIM_NETWORK_PUK_ENTRY" msgid="768060297218652809">"Унесите PUK"</string>
+ <string name="PERSOSUBSTATE_SIM_NETWORK_SUBSET_PUK_ENTRY" msgid="7129527319490548930">"Унесите PUK"</string>
+ <string name="PERSOSUBSTATE_SIM_CORPORATE_PUK_ENTRY" msgid="2876126640607573252">"Унесите PUK"</string>
+ <string name="PERSOSUBSTATE_SIM_SERVICE_PROVIDER_PUK_ENTRY" msgid="8952595089930109282">"Унесите PUK"</string>
+ <string name="PERSOSUBSTATE_SIM_SIM_PUK_ENTRY" msgid="3013902515773728996">"Унесите PUK"</string>
+ <string name="PERSOSUBSTATE_RUIM_NETWORK1_ENTRY" msgid="2974411408893410289">"PIN за откључавање RUIM мреже 1"</string>
+ <string name="PERSOSUBSTATE_RUIM_NETWORK2_ENTRY" msgid="687618528751880721">"PIN за откључавање RUIM мреже 2"</string>
+ <string name="PERSOSUBSTATE_RUIM_HRPD_ENTRY" msgid="6810596579655575381">"PIN за откључавање RUIM hrpd-а"</string>
+ <string name="PERSOSUBSTATE_RUIM_CORPORATE_ENTRY" msgid="2715929642540980259">"PIN за откључавање пословне RUIM картице"</string>
+ <string name="PERSOSUBSTATE_RUIM_SERVICE_PROVIDER_ENTRY" msgid="8557791623303951590">"PIN за откључавање RUIM картице добављача услуге"</string>
+ <string name="PERSOSUBSTATE_RUIM_RUIM_ENTRY" msgid="7382468767274580323">"PIN за откључавање RUIM картице"</string>
+ <string name="PERSOSUBSTATE_RUIM_NETWORK1_PUK_ENTRY" msgid="6730880791104286987">"Унесите PUK"</string>
+ <string name="PERSOSUBSTATE_RUIM_NETWORK2_PUK_ENTRY" msgid="6432126539782267026">"Унесите PUK"</string>
+ <string name="PERSOSUBSTATE_RUIM_HRPD_PUK_ENTRY" msgid="1730510161529488920">"Унесите PUK"</string>
+ <string name="PERSOSUBSTATE_RUIM_SERVICE_PROVIDER_PUK_ENTRY" msgid="3369885925003346830">"Унесите PUK"</string>
+ <string name="PERSOSUBSTATE_RUIM_RUIM_PUK_ENTRY" msgid="9129139686191167829">"Унесите PUK"</string>
+ <string name="PERSOSUBSTATE_RUIM_CORPORATE_PUK_ENTRY" msgid="2869929685874615358">"Унесите PUK"</string>
+ <string name="PERSOSUBSTATE_SIM_SPN_ENTRY" msgid="1238663472392741771">"PIN за откључавање SPN-а"</string>
+ <string name="PERSOSUBSTATE_SIM_SP_EHPLMN_ENTRY" msgid="3988705848553894358">"PIN за откључавање SP еквивалентног матичног PLMN-а"</string>
+ <string name="PERSOSUBSTATE_SIM_ICCID_ENTRY" msgid="6186770686690993200">"PIN за откључавање ICCID-а"</string>
+ <string name="PERSOSUBSTATE_SIM_IMPI_ENTRY" msgid="7043865376145617024">"PIN за откључавање IMPI-ја"</string>
+ <string name="PERSOSUBSTATE_SIM_NS_SP_ENTRY" msgid="6144227308185112176">"PIN за откључавање добављача услуге подскупа мреже"</string>
+ <string name="PERSOSUBSTATE_SIM_NETWORK_IN_PROGRESS" msgid="4233355366318061180">"Захтева се откључавање SIM мреже…"</string>
+ <string name="PERSOSUBSTATE_SIM_NETWORK_SUBSET_IN_PROGRESS" msgid="6742563947637715645">"Захтева се откључавање подскупа SIM мреже…"</string>
+ <string name="PERSOSUBSTATE_SIM_SERVICE_PROVIDER_IN_PROGRESS" msgid="2033399698172403560">"Захтева се откључавање добављача услуге SIM картице…"</string>
+ <string name="PERSOSUBSTATE_SIM_CORPORATE_IN_PROGRESS" msgid="4795977251920732254">"Захтева се откључавање пословне SIM картице…"</string>
+ <string name="PERSOSUBSTATE_SIM_NETWORK_PUK_IN_PROGRESS" msgid="1090425878157254446">"Захтева се откључавање помоћу PUK-а…"</string>
+ <string name="PERSOSUBSTATE_SIM_NETWORK_SUBSET_PUK_IN_PROGRESS" msgid="6476898876518094438">"Захтева се откључавање помоћу PUK-а…"</string>
+ <string name="PERSOSUBSTATE_SIM_CORPORATE_PUK_IN_PROGRESS" msgid="6006806734293747731">"Захтева се откључавање помоћу PUK-а…"</string>
+ <string name="PERSOSUBSTATE_SIM_SERVICE_PROVIDER_PUK_IN_PROGRESS" msgid="6546680489620881893">"Захтева се откључавање помоћу PUK-а…"</string>
+ <string name="PERSOSUBSTATE_SIM_SIM_PUK_IN_PROGRESS" msgid="3506845511000727015">"Захтева се откључавање помоћу PUK-а…"</string>
+ <string name="PERSOSUBSTATE_SIM_SIM_IN_PROGRESS" msgid="6709169861932992750">"Захтева се откључавање SIM картице…"</string>
+ <string name="PERSOSUBSTATE_RUIM_NETWORK1_IN_PROGRESS" msgid="4013870911606478520">"Захтева се откључавање RUIM мреже 1…"</string>
+ <string name="PERSOSUBSTATE_RUIM_NETWORK2_IN_PROGRESS" msgid="9032651188219523434">"Захтева се откључавање RUIM мреже 2…"</string>
+ <string name="PERSOSUBSTATE_RUIM_HRPD_IN_PROGRESS" msgid="6584576506344491207">"Захтева се откључавање RUIM hrpd-а…"</string>
+ <string name="PERSOSUBSTATE_RUIM_SERVICE_PROVIDER_IN_PROGRESS" msgid="830981927724888114">"Захтева се откључавање RUIM картице добављача услуге…"</string>
+ <string name="PERSOSUBSTATE_RUIM_CORPORATE_IN_PROGRESS" msgid="7851790973098894802">"Захтева се откључавање пословне RUIM картице…"</string>
+ <string name="PERSOSUBSTATE_SIM_SPN_IN_PROGRESS" msgid="1149560739586960121">"Захтева се откључавање SPN-а…"</string>
+ <string name="PERSOSUBSTATE_SIM_SP_EHPLMN_IN_PROGRESS" msgid="5708964693522116025">"Захтева се откључавање SP еквивалентног матичног PLMN-а…"</string>
+ <string name="PERSOSUBSTATE_SIM_ICCID_IN_PROGRESS" msgid="7288103122966483455">"Захтева се откључавање ICCID-а…"</string>
+ <string name="PERSOSUBSTATE_SIM_IMPI_IN_PROGRESS" msgid="4036752174056147753">"Захтева се откључавање IMPI-ја…"</string>
+ <string name="PERSOSUBSTATE_SIM_NS_SP_IN_PROGRESS" msgid="5089536274515338566">"Захтева се откључавање добављача услуге подскупа мреже…"</string>
+ <string name="PERSOSUBSTATE_RUIM_RUIM_IN_PROGRESS" msgid="6737197986936251958">"Захтева се откључавање RUIM картице…"</string>
+ <string name="PERSOSUBSTATE_RUIM_NETWORK1_PUK_IN_PROGRESS" msgid="5658767775619998623">"Захтева се откључавање помоћу PUK-а…"</string>
+ <string name="PERSOSUBSTATE_RUIM_NETWORK2_PUK_IN_PROGRESS" msgid="665978313257653727">"Захтева се откључавање помоћу PUK-а…"</string>
+ <string name="PERSOSUBSTATE_RUIM_HRPD_PUK_IN_PROGRESS" msgid="3857142652251836850">"Захтева се откључавање помоћу PUK-а…"</string>
+ <string name="PERSOSUBSTATE_RUIM_CORPORATE_PUK_IN_PROGRESS" msgid="2695664012344346788">"Захтева се откључавање помоћу PUK-а…"</string>
+ <string name="PERSOSUBSTATE_RUIM_SERVICE_PROVIDER_PUK_IN_PROGRESS" msgid="2695678959963807782">"Захтева се откључавање помоћу PUK-а…"</string>
+ <string name="PERSOSUBSTATE_RUIM_RUIM_PUK_IN_PROGRESS" msgid="1230605365926493599">"Захтева се откључавање помоћу PUK-а…"</string>
+ <string name="PERSOSUBSTATE_SIM_NETWORK_ERROR" msgid="1924844017037151535">"Захтев за откључавање SIM мреже није успео."</string>
+ <string name="PERSOSUBSTATE_SIM_NETWORK_SUBSET_ERROR" msgid="3372797822292089708">"Захтев за откључавање подскупа SIM мреже је успео."</string>
+ <string name="PERSOSUBSTATE_SIM_SERVICE_PROVIDER_ERROR" msgid="1878443146720411381">"Захтев за откључавање добављача услуге SIM картице није успео."</string>
+ <string name="PERSOSUBSTATE_SIM_CORPORATE_ERROR" msgid="7664778312218023192">"Захтев за откључавање пословне SIM картице није успео."</string>
+ <string name="PERSOSUBSTATE_SIM_SIM_ERROR" msgid="2472944311643350302">"Захтев за откључавање SIM картице није успео."</string>
+ <string name="PERSOSUBSTATE_RUIM_NETWORK1_ERROR" msgid="828089694480999120">"Захтев за откључавање RUIM мреже 1 није успео."</string>
+ <string name="PERSOSUBSTATE_RUIM_NETWORK2_ERROR" msgid="17619001007092511">"Захтев за откључавање RUIM мреже 2 није успео."</string>
+ <string name="PERSOSUBSTATE_RUIM_HRPD_ERROR" msgid="807214229604353614">"Захтев за откључавање RUIM hrpd-а није успео."</string>
+ <string name="PERSOSUBSTATE_RUIM_CORPORATE_ERROR" msgid="8644184447744175747">"Захтев за откључавање пословне RUIM картице није успео."</string>
+ <string name="PERSOSUBSTATE_RUIM_SERVICE_PROVIDER_ERROR" msgid="3801002648649640407">"Захтев за откључавање RUIM картице добављача услуге није успео."</string>
+ <string name="PERSOSUBSTATE_RUIM_RUIM_ERROR" msgid="707397021218680753">"Захтев за откључавање RUIM картице није успео."</string>
+ <string name="PERSOSUBSTATE_SIM_NETWORK_PUK_ERROR" msgid="894358680773257820">"Откључавање помоћу PUK-а није успело."</string>
+ <string name="PERSOSUBSTATE_SIM_NETWORK_SUBSET_PUK_ERROR" msgid="352466878146726991">"Откључавање помоћу PUK-а није успело."</string>
+ <string name="PERSOSUBSTATE_SIM_CORPORATE_PUK_ERROR" msgid="7353389721907138671">"Откључавање помоћу PUK-а није успело."</string>
+ <string name="PERSOSUBSTATE_SIM_SERVICE_PROVIDER_PUK_ERROR" msgid="2655263155490857920">"Откључавање помоћу PUK-а није успело."</string>
+ <string name="PERSOSUBSTATE_SIM_SIM_PUK_ERROR" msgid="6903740900892931310">"Откључавање помоћу PUK-а није успело."</string>
+ <string name="PERSOSUBSTATE_RUIM_NETWORK1_PUK_ERROR" msgid="5165901670447518687">"Откључавање помоћу PUK-а није успело."</string>
+ <string name="PERSOSUBSTATE_RUIM_NETWORK2_PUK_ERROR" msgid="2856763216589267623">"Откључавање помоћу PUK-а није успело."</string>
+ <string name="PERSOSUBSTATE_RUIM_HRPD_PUK_ERROR" msgid="817542684437829139">"Откључавање помоћу PUK-а није успело."</string>
+ <string name="PERSOSUBSTATE_RUIM_SERVICE_PROVIDER_PUK_ERROR" msgid="5178635064113393143">"Откључавање помоћу PUK-а није успело."</string>
+ <string name="PERSOSUBSTATE_RUIM_RUIM_PUK_ERROR" msgid="5391587926974531008">"Откључавање помоћу PUK-а није успело."</string>
+ <string name="PERSOSUBSTATE_RUIM_CORPORATE_PUK_ERROR" msgid="4895494864493315868">"Откључавање помоћу PUK-а није успело."</string>
+ <string name="PERSOSUBSTATE_SIM_SPN_ERROR" msgid="9017576601595353649">"Захтев за откључавање SPN-а није успео."</string>
+ <string name="PERSOSUBSTATE_SIM_SP_EHPLMN_ERROR" msgid="1116993930995545742">"Захтев за откључавање SP еквивалентног матичног PLMN-а није успео."</string>
+ <string name="PERSOSUBSTATE_SIM_ICCID_ERROR" msgid="7559167306794441462">"Захтев за откључавање ICCID-а није успео."</string>
+ <string name="PERSOSUBSTATE_SIM_IMPI_ERROR" msgid="2782926139511136588">"Захтев за откључавање IMPI-ја није успео."</string>
+ <string name="PERSOSUBSTATE_SIM_NS_SP_ERROR" msgid="1890493954453456758">"Захтев за откључавање добављача услуге подскупа мреже није успео."</string>
+ <string name="PERSOSUBSTATE_SIM_NETWORK_SUCCESS" msgid="4886243367747126325">"Откључавање SIM мреже је успело."</string>
+ <string name="PERSOSUBSTATE_SIM_NETWORK_SUBSET_SUCCESS" msgid="4053809277733513987">"Откључавање подскупа SIM мреже је успело."</string>
+ <string name="PERSOSUBSTATE_SIM_SERVICE_PROVIDER_SUCCESS" msgid="8249342930499801740">"Откључавање добављача услуге SIM картице је успело."</string>
+ <string name="PERSOSUBSTATE_SIM_CORPORATE_SUCCESS" msgid="2339794542560381270">"Откључавање пословне SIM картице је успело."</string>
+ <string name="PERSOSUBSTATE_SIM_SIM_SUCCESS" msgid="6975608174152828954">"Откључавање SIM картице је успело."</string>
+ <string name="PERSOSUBSTATE_RUIM_NETWORK1_SUCCESS" msgid="2846699261330463192">"Откључавање RUIM мреже 1 је успело."</string>
+ <string name="PERSOSUBSTATE_RUIM_NETWORK2_SUCCESS" msgid="5335414726057102801">"Откључавање RUIM мреже 2 је успело."</string>
+ <string name="PERSOSUBSTATE_RUIM_HRPD_SUCCESS" msgid="8868100318474971969">"Откључавање RUIM hrpd-а је успело."</string>
+ <string name="PERSOSUBSTATE_RUIM_SERVICE_PROVIDER_SUCCESS" msgid="6020936629725666932">"Захтев за откључавање RUIM картице добављача услуге је успео."</string>
+ <string name="PERSOSUBSTATE_RUIM_CORPORATE_SUCCESS" msgid="6944873647584595489">"Откључавање пословне RUIM картице је успело."</string>
+ <string name="PERSOSUBSTATE_RUIM_RUIM_SUCCESS" msgid="2526483514124121988">"Откључавање RUIM картице је успело."</string>
+ <string name="PERSOSUBSTATE_SIM_NETWORK_PUK_SUCCESS" msgid="7662200333621664621">"Откључавање помоћу PUK-а је успело."</string>
+ <string name="PERSOSUBSTATE_SIM_NETWORK_SUBSET_PUK_SUCCESS" msgid="2861223407953766632">"Откључавање помоћу PUK-а је успело."</string>
+ <string name="PERSOSUBSTATE_SIM_CORPORATE_PUK_SUCCESS" msgid="5345648571175243272">"Откључавање помоћу PUK-а је успело."</string>
+ <string name="PERSOSUBSTATE_SIM_SERVICE_PROVIDER_PUK_SUCCESS" msgid="3725278343103422466">"Откључавање помоћу PUK-а је успело."</string>
+ <string name="PERSOSUBSTATE_SIM_SIM_PUK_SUCCESS" msgid="6998502547560297983">"Откључавање помоћу PUK-а је успело."</string>
+ <string name="PERSOSUBSTATE_RUIM_NETWORK1_PUK_SUCCESS" msgid="8555433771162560361">"Откључавање помоћу PUK-а је успело."</string>
+ <string name="PERSOSUBSTATE_RUIM_NETWORK2_PUK_SUCCESS" msgid="3555767296933606232">"Откључавање помоћу PUK-а је успело."</string>
+ <string name="PERSOSUBSTATE_RUIM_HRPD_PUK_SUCCESS" msgid="6778051818199974237">"Откључавање помоћу PUK-а је успело."</string>
+ <string name="PERSOSUBSTATE_RUIM_CORPORATE_PUK_SUCCESS" msgid="4080108758498911429">"Откључавање помоћу PUK-а је успело."</string>
+ <string name="PERSOSUBSTATE_RUIM_SERVICE_PROVIDER_PUK_SUCCESS" msgid="7873675303000794343">"Откључавање помоћу PUK-а је успело."</string>
+ <string name="PERSOSUBSTATE_RUIM_RUIM_PUK_SUCCESS" msgid="1763198215069819523">"Откључавање помоћу PUK-а је успело."</string>
+ <string name="PERSOSUBSTATE_SIM_SPN_SUCCESS" msgid="2053891977727320532">"Откључавање SPN-а је успело."</string>
+ <string name="PERSOSUBSTATE_SIM_SP_EHPLMN_SUCCESS" msgid="8146602361895007345">"Откључавање SP еквивалентног матичног PLMN-а је успело."</string>
+ <string name="PERSOSUBSTATE_SIM_ICCID_SUCCESS" msgid="8058678548991999545">"Откључавање ICCID-а је успело."</string>
+ <string name="PERSOSUBSTATE_SIM_IMPI_SUCCESS" msgid="2545608067978550571">"Откључавање IMPI-ја је успело."</string>
+ <string name="PERSOSUBSTATE_SIM_NS_SP_SUCCESS" msgid="4352382949744625007">"Захтев за откључавање добављача услуге подскупа мреже је успео."</string>
<string name="config_pdp_reject_dialog_title" msgid="4072057179246785727"></string>
<string name="config_pdp_reject_user_authentication_failed" msgid="4531693033885744689"></string>
<string name="config_pdp_reject_service_not_subscribed" msgid="8190338397128671588"></string>
<string name="config_pdp_reject_multi_conn_to_same_pdn_not_allowed" msgid="6024904218067254186"></string>
- <string name="window_magnification_prompt_title" msgid="2876703640772778215">"Nova podešavanja uvećanja"</string>
- <string name="window_magnification_prompt_content" msgid="8159173903032344891">"Sada možete da uvećate deo ekrana"</string>
- <string name="turn_on_magnification_settings_action" msgid="8521433346684847700">"Uključite u Podešavanjima"</string>
- <string name="dismiss_action" msgid="1728820550388704784">"Odbaci"</string>
- <string name="sensor_privacy_start_use_mic_notification_content_title" msgid="2420858361276370367">"Odblokirajte mikrofon uređaja"</string>
- <string name="sensor_privacy_start_use_camera_notification_content_title" msgid="7287720213963466672">"Odblokirajte kameru uređaja"</string>
- <string name="sensor_privacy_start_use_notification_content_text" msgid="7595608891015777346">"Za <b><xliff:g id="APP">%s</xliff:g></b> i sve aplikacije i usluge"</string>
- <string name="sensor_privacy_start_use_dialog_turn_on_button" msgid="7089318886628390827">"Odblokiraj"</string>
- <string name="sensor_privacy_notification_channel_label" msgid="936036783155261349">"Privatnost senzora"</string>
- <string name="splash_screen_view_icon_description" msgid="180638751260598187">"Ikona aplikacije"</string>
- <string name="splash_screen_view_branding_description" msgid="7911129347402728216">"Imidž brenda aplikacije"</string>
- <string name="view_and_control_notification_title" msgid="4300765399209912240">"Proverite podešavanja pristupa"</string>
- <string name="view_and_control_notification_content" msgid="8003766498562604034">"<xliff:g id="SERVICE_NAME">%s</xliff:g> može da pregleda i kontroliše ekran. Dodirnite da biste pregledali."</string>
- <string name="ui_translation_accessibility_translated_text" msgid="3197547218178944544">"<xliff:g id="MESSAGE">%1$s</xliff:g> Prevedeno."</string>
- <string name="ui_translation_accessibility_translation_finished" msgid="3057830947610088465">"Poruka je prevedena sa jezika <xliff:g id="FROM_LANGUAGE">%1$s</xliff:g> na <xliff:g id="TO_LANGUAGE">%2$s</xliff:g>."</string>
- <string name="notification_channel_abusive_bg_apps" msgid="6092140213264920355">"Aktivnost u pozadini"</string>
- <string name="notification_title_abusive_bg_apps" msgid="994230770856147656">"Aplikacija vam prazni bateriju"</string>
- <string name="notification_title_long_running_fgs" msgid="8170284286477131587">"Aplikacija je i dalje aktivna"</string>
- <string name="notification_content_abusive_bg_apps" msgid="5296898075922695259">"Aplikacija <xliff:g id="APP">%1$s</xliff:g> radi u pozadini. Dodirnite da biste upravljali potrošnjom baterije."</string>
- <string name="notification_content_long_running_fgs" msgid="8258193410039977101">"<xliff:g id="APP">%1$s</xliff:g> može da utiče na trajanje baterije. Dodirnite da biste pregledali aktivne aplikacije."</string>
- <string name="notification_action_check_bg_apps" msgid="4758877443365362532">"Proverite aktivne aplikacije"</string>
- <string name="vdm_camera_access_denied" product="default" msgid="6102378580971542473">"Ne može da se pristupi kameri telefona sa <xliff:g id="DEVICE">%1$s</xliff:g> uređaja"</string>
- <string name="vdm_camera_access_denied" product="tablet" msgid="6895968310395249076">"Ne može da se pristupi kameri tableta sa <xliff:g id="DEVICE">%1$s</xliff:g> uređaja"</string>
- <string name="vdm_secure_window" msgid="161700398158812314">"Ovom ne možete da pristupate tokom strimovanja. Probajte na telefonu."</string>
- <string name="vdm_pip_blocked" msgid="4036107522497281397">"Ne možete da gledate sliku u slici pri strimovanju"</string>
- <string name="system_locale_title" msgid="711882686834677268">"Podrazumevani sistemski"</string>
- <string name="default_card_name" msgid="9198284935962911468">"KARTICA <xliff:g id="CARDNUMBER">%d</xliff:g>"</string>
- <string name="permlab_companionProfileWatch" msgid="2457738382085872542">"Dozvola za profil pratećeg sata za upravljanje satovima"</string>
- <string name="permdesc_companionProfileWatch" msgid="5655698581110449397">"Dozvoljava pratećoj aplikaciji da upravlja satovima."</string>
- <string name="permlab_observeCompanionDevicePresence" msgid="9008994909653990465">"Nadgledanje prisustva pratećeg uređaja"</string>
- <string name="permdesc_observeCompanionDevicePresence" msgid="3011699826788697852">"Dozvoljava pratećoj aplikaciji da nadgleda prisustvo pratećeg uređaja kada su uređaji u blizini ili daleko."</string>
- <string name="permlab_deliverCompanionMessages" msgid="3931552294842980887">"Isporuka poruka iz prateće aplikacije"</string>
- <string name="permdesc_deliverCompanionMessages" msgid="2170847384281412850">"Dozvoljava pratećoj aplikaciji da šalje prateće poruke na druge uređaje."</string>
- <string name="permlab_startForegroundServicesFromBackground" msgid="6363004936218638382">"Pokretanje usluga u prvom planu iz pozadine"</string>
- <string name="permdesc_startForegroundServicesFromBackground" msgid="4071826571656001537">"Dozvoljava pratećoj aplikaciji da pokrene usluge u prvom planu iz pozadine."</string>
+ <string name="window_magnification_prompt_title" msgid="2876703640772778215">"Нова подешавања увећања"</string>
+ <string name="window_magnification_prompt_content" msgid="8159173903032344891">"Сада можете да увећате део екрана"</string>
+ <string name="turn_on_magnification_settings_action" msgid="8521433346684847700">"Укључите у Подешавањима"</string>
+ <string name="dismiss_action" msgid="1728820550388704784">"Одбаци"</string>
+ <string name="sensor_privacy_start_use_mic_notification_content_title" msgid="2420858361276370367">"Одблокирајте микрофон уређаја"</string>
+ <string name="sensor_privacy_start_use_camera_notification_content_title" msgid="7287720213963466672">"Одблокирајте камеру уређаја"</string>
+ <string name="sensor_privacy_start_use_notification_content_text" msgid="7595608891015777346">"За <b><xliff:g id="APP">%s</xliff:g></b> и све апликације и услуге"</string>
+ <string name="sensor_privacy_start_use_dialog_turn_on_button" msgid="7089318886628390827">"Одблокирај"</string>
+ <string name="sensor_privacy_notification_channel_label" msgid="936036783155261349">"Приватност сензора"</string>
+ <string name="splash_screen_view_icon_description" msgid="180638751260598187">"Икона апликације"</string>
+ <string name="splash_screen_view_branding_description" msgid="7911129347402728216">"Имиџ бренда апликације"</string>
+ <string name="view_and_control_notification_title" msgid="4300765399209912240">"Проверите подешавања приступа"</string>
+ <string name="view_and_control_notification_content" msgid="8003766498562604034">"<xliff:g id="SERVICE_NAME">%s</xliff:g> може да прегледа и контролише екран. Додирните да бисте прегледали."</string>
+ <string name="ui_translation_accessibility_translated_text" msgid="3197547218178944544">"<xliff:g id="MESSAGE">%1$s</xliff:g> Преведено."</string>
+ <string name="ui_translation_accessibility_translation_finished" msgid="3057830947610088465">"Порука је преведена са језика <xliff:g id="FROM_LANGUAGE">%1$s</xliff:g> на <xliff:g id="TO_LANGUAGE">%2$s</xliff:g>."</string>
+ <string name="notification_channel_abusive_bg_apps" msgid="6092140213264920355">"Активност у позадини"</string>
+ <string name="notification_title_abusive_bg_apps" msgid="994230770856147656">"Апликација вам празни батерију"</string>
+ <string name="notification_title_long_running_fgs" msgid="8170284286477131587">"Апликација је и даље активна"</string>
+ <string name="notification_content_abusive_bg_apps" msgid="5296898075922695259">"Апликација <xliff:g id="APP">%1$s</xliff:g> ради у позадини. Додирните да бисте управљали потрошњом батерије."</string>
+ <string name="notification_content_long_running_fgs" msgid="8258193410039977101">"<xliff:g id="APP">%1$s</xliff:g> може да утиче на трајање батерије. Додирните да бисте прегледали активне апликације."</string>
+ <string name="notification_action_check_bg_apps" msgid="4758877443365362532">"Проверите активне апликације"</string>
+ <string name="vdm_camera_access_denied" product="default" msgid="6102378580971542473">"Не може да се приступи камери телефона са <xliff:g id="DEVICE">%1$s</xliff:g> уређаја"</string>
+ <string name="vdm_camera_access_denied" product="tablet" msgid="6895968310395249076">"Не може да се приступи камери таблета са <xliff:g id="DEVICE">%1$s</xliff:g> уређаја"</string>
+ <string name="vdm_secure_window" msgid="161700398158812314">"Овом не можете да приступате током стримовања. Пробајте на телефону."</string>
+ <string name="vdm_pip_blocked" msgid="4036107522497281397">"Не можете да гледате слику у слици при стримовању"</string>
+ <string name="system_locale_title" msgid="711882686834677268">"Подразумевани системски"</string>
+ <string name="default_card_name" msgid="9198284935962911468">"КАРТИЦА <xliff:g id="CARDNUMBER">%d</xliff:g>"</string>
+ <string name="permlab_companionProfileWatch" msgid="2457738382085872542">"Дозвола за профил пратећег сата за управљање сатовима"</string>
+ <string name="permdesc_companionProfileWatch" msgid="5655698581110449397">"Дозвољава пратећој апликацији да управља сатовима."</string>
+ <string name="permlab_observeCompanionDevicePresence" msgid="9008994909653990465">"Надгледање присуства пратећег уређаја"</string>
+ <string name="permdesc_observeCompanionDevicePresence" msgid="3011699826788697852">"Дозвољава пратећој апликацији да надгледа присуство пратећег уређаја када су уређаји у близини или далеко."</string>
+ <string name="permlab_deliverCompanionMessages" msgid="3931552294842980887">"Испорука порука из пратеће апликације"</string>
+ <string name="permdesc_deliverCompanionMessages" msgid="2170847384281412850">"Дозвољава пратећој апликацији да шаље пратеће поруке на друге уређаје."</string>
+ <string name="permlab_startForegroundServicesFromBackground" msgid="6363004936218638382">"Покретање услуга у првом плану из позадине"</string>
+ <string name="permdesc_startForegroundServicesFromBackground" msgid="4071826571656001537">"Дозвољава пратећој апликацији да покрене услуге у првом плану из позадине."</string>
+ <!-- no translation found for mic_access_on_toast (2666925317663845156) -->
+ <skip />
+ <!-- no translation found for mic_access_off_toast (8111040892954242437) -->
+ <skip />
</resources>
diff --git a/core/res/res/values-be/strings.xml b/core/res/res/values-be/strings.xml
index 45e14c0..e136275 100644
--- a/core/res/res/values-be/strings.xml
+++ b/core/res/res/values-be/strings.xml
@@ -2354,4 +2354,8 @@
<string name="permdesc_deliverCompanionMessages" msgid="2170847384281412850">"Спадарожная праграма зможа дастаўляць паведамленні на іншыя прылады."</string>
<string name="permlab_startForegroundServicesFromBackground" msgid="6363004936218638382">"Запуск актыўных сэрвісаў з фонавага рэжыму"</string>
<string name="permdesc_startForegroundServicesFromBackground" msgid="4071826571656001537">"Спадарожная праграма зможа запускаць актыўныя сэрвісы з фонавага рэжыму."</string>
+ <!-- no translation found for mic_access_on_toast (2666925317663845156) -->
+ <skip />
+ <!-- no translation found for mic_access_off_toast (8111040892954242437) -->
+ <skip />
</resources>
diff --git a/core/res/res/values-bg/strings.xml b/core/res/res/values-bg/strings.xml
index 8acc09b..fd6bd3e 100644
--- a/core/res/res/values-bg/strings.xml
+++ b/core/res/res/values-bg/strings.xml
@@ -2352,4 +2352,8 @@
<string name="permdesc_deliverCompanionMessages" msgid="2170847384281412850">"Разрешава на дадено придружаващо приложение да доставя придружаващи съобщения до други устройства."</string>
<string name="permlab_startForegroundServicesFromBackground" msgid="6363004936218638382">"Стартиране на услуги на преден план при изпълнение на заден план"</string>
<string name="permdesc_startForegroundServicesFromBackground" msgid="4071826571656001537">"Разрешава на дадено придружаващо приложение да стартира услуги на преден план, докато се изпълнява на заден план."</string>
+ <!-- no translation found for mic_access_on_toast (2666925317663845156) -->
+ <skip />
+ <!-- no translation found for mic_access_off_toast (8111040892954242437) -->
+ <skip />
</resources>
diff --git a/core/res/res/values-bn/strings.xml b/core/res/res/values-bn/strings.xml
index 2aa76b9..4da5520 100644
--- a/core/res/res/values-bn/strings.xml
+++ b/core/res/res/values-bn/strings.xml
@@ -2352,4 +2352,8 @@
<string name="permdesc_deliverCompanionMessages" msgid="2170847384281412850">"অন্যান্য ডিভাইসে কম্প্যানিয়ন মেসেজ ডেলিভার করতে কম্প্যানিয়ন অ্যাপকে অনুমতি দেয়।"</string>
<string name="permlab_startForegroundServicesFromBackground" msgid="6363004936218638382">"ব্যাকগ্রাউন্ড থেকে ফোরগ্রাউন্ড পরিষেবা চালু করা"</string>
<string name="permdesc_startForegroundServicesFromBackground" msgid="4071826571656001537">"কম্প্যানিয়ন অ্যাপকে, ব্যাকগ্রাউন্ড থেকে ফোরগ্রাউন্ড পরিষেবা চালু করার অনুমতি দেয়।"</string>
+ <!-- no translation found for mic_access_on_toast (2666925317663845156) -->
+ <skip />
+ <!-- no translation found for mic_access_off_toast (8111040892954242437) -->
+ <skip />
</resources>
diff --git a/core/res/res/values-bs/strings.xml b/core/res/res/values-bs/strings.xml
index 4f50c18..67574ef 100644
--- a/core/res/res/values-bs/strings.xml
+++ b/core/res/res/values-bs/strings.xml
@@ -2353,4 +2353,8 @@
<string name="permdesc_deliverCompanionMessages" msgid="2170847384281412850">"Dozvoljava pratećoj aplikaciji da isporučuje prateće poruke na drugim uređajima."</string>
<string name="permlab_startForegroundServicesFromBackground" msgid="6363004936218638382">"Pokreni usluge u prvom planu iz pozadine"</string>
<string name="permdesc_startForegroundServicesFromBackground" msgid="4071826571656001537">"Dozvoljava pratećoj aplikaciji da iz pozadine pokrene usluge u prvom planu."</string>
+ <!-- no translation found for mic_access_on_toast (2666925317663845156) -->
+ <skip />
+ <!-- no translation found for mic_access_off_toast (8111040892954242437) -->
+ <skip />
</resources>
diff --git a/core/res/res/values-ca/strings.xml b/core/res/res/values-ca/strings.xml
index 55f8c5b..080501d 100644
--- a/core/res/res/values-ca/strings.xml
+++ b/core/res/res/values-ca/strings.xml
@@ -2353,4 +2353,8 @@
<string name="permdesc_deliverCompanionMessages" msgid="2170847384281412850">"Permet que una aplicació complementària enviï missatges complementaris a altres dispositius."</string>
<string name="permlab_startForegroundServicesFromBackground" msgid="6363004936218638382">"Inicia serveis en primer pla des d\'un segon pla"</string>
<string name="permdesc_startForegroundServicesFromBackground" msgid="4071826571656001537">"Permet que una aplicació complementària iniciï serveis en primer pla des d\'un segon pla."</string>
+ <!-- no translation found for mic_access_on_toast (2666925317663845156) -->
+ <skip />
+ <!-- no translation found for mic_access_off_toast (8111040892954242437) -->
+ <skip />
</resources>
diff --git a/core/res/res/values-cs/strings.xml b/core/res/res/values-cs/strings.xml
index 46d8cfc..2a6383f 100644
--- a/core/res/res/values-cs/strings.xml
+++ b/core/res/res/values-cs/strings.xml
@@ -2354,4 +2354,8 @@
<string name="permdesc_deliverCompanionMessages" msgid="2170847384281412850">"Umožňuje doprovodné aplikaci doručovat doprovodné zprávy do jiných zařízení."</string>
<string name="permlab_startForegroundServicesFromBackground" msgid="6363004936218638382">"Spouštět z pozadí služby v popředí"</string>
<string name="permdesc_startForegroundServicesFromBackground" msgid="4071826571656001537">"Umožňuje doprovodné aplikaci spouštět z pozadí služby v popředí."</string>
+ <!-- no translation found for mic_access_on_toast (2666925317663845156) -->
+ <skip />
+ <!-- no translation found for mic_access_off_toast (8111040892954242437) -->
+ <skip />
</resources>
diff --git a/core/res/res/values-da/strings.xml b/core/res/res/values-da/strings.xml
index 6e1a0c2..74589fe 100644
--- a/core/res/res/values-da/strings.xml
+++ b/core/res/res/values-da/strings.xml
@@ -2352,4 +2352,8 @@
<string name="permdesc_deliverCompanionMessages" msgid="2170847384281412850">"Tillader, at en medfølgende app kan levere medfølgende meddelelser til andre enheder."</string>
<string name="permlab_startForegroundServicesFromBackground" msgid="6363004936218638382">"Start tjenester i forgrunden via tilladelser til tjenester i baggrunden"</string>
<string name="permdesc_startForegroundServicesFromBackground" msgid="4071826571656001537">"Tillader, at en medfølgende app kan starte tjenester i forgrunden via tilladelser til tjenester i baggrunden."</string>
+ <!-- no translation found for mic_access_on_toast (2666925317663845156) -->
+ <skip />
+ <!-- no translation found for mic_access_off_toast (8111040892954242437) -->
+ <skip />
</resources>
diff --git a/core/res/res/values-de/strings.xml b/core/res/res/values-de/strings.xml
index ccc2a19..9cb496e 100644
--- a/core/res/res/values-de/strings.xml
+++ b/core/res/res/values-de/strings.xml
@@ -2352,4 +2352,8 @@
<string name="permdesc_deliverCompanionMessages" msgid="2170847384281412850">"Ermöglicht einer Companion-App, Companion-Nachrichten an andere Geräte zu senden."</string>
<string name="permlab_startForegroundServicesFromBackground" msgid="6363004936218638382">"Dienste im Vordergrund aus dem Hintergrund starten"</string>
<string name="permdesc_startForegroundServicesFromBackground" msgid="4071826571656001537">"Ermöglicht einer Companion-App, Dienste im Vordergrund aus dem Hintergrund zu starten."</string>
+ <!-- no translation found for mic_access_on_toast (2666925317663845156) -->
+ <skip />
+ <!-- no translation found for mic_access_off_toast (8111040892954242437) -->
+ <skip />
</resources>
diff --git a/core/res/res/values-el/strings.xml b/core/res/res/values-el/strings.xml
index 85d3455..dc3bc45 100644
--- a/core/res/res/values-el/strings.xml
+++ b/core/res/res/values-el/strings.xml
@@ -2352,4 +2352,8 @@
<string name="permdesc_deliverCompanionMessages" msgid="2170847384281412850">"Επιτρέπει σε μια συνοδευτική εφαρμογή να παρέχει μηνύματα σε άλλες συσκευές."</string>
<string name="permlab_startForegroundServicesFromBackground" msgid="6363004936218638382">"Εκκίνηση υπηρεσιών στο προσκήνιο από το παρασκήνιο"</string>
<string name="permdesc_startForegroundServicesFromBackground" msgid="4071826571656001537">"Επιτρέπει σε μια συνοδευτική εφαρμογή να εκκινεί υπηρεσίες στο προσκήνιο από το παρασκήνιο."</string>
+ <!-- no translation found for mic_access_on_toast (2666925317663845156) -->
+ <skip />
+ <!-- no translation found for mic_access_off_toast (8111040892954242437) -->
+ <skip />
</resources>
diff --git a/core/res/res/values-en-rAU/strings.xml b/core/res/res/values-en-rAU/strings.xml
index abe900d..1b1a0c4 100644
--- a/core/res/res/values-en-rAU/strings.xml
+++ b/core/res/res/values-en-rAU/strings.xml
@@ -43,10 +43,8 @@
<string name="mismatchPin" msgid="2929611853228707473">"The PINs that you typed don\'t match."</string>
<string name="invalidPin" msgid="7542498253319440408">"Type a PIN that is 4 to 8 numbers."</string>
<string name="invalidPuk" msgid="8831151490931907083">"Type a PUK that is 8 numbers or longer."</string>
- <!-- no translation found for needPuk (3503414069503752211) -->
- <skip />
- <!-- no translation found for needPuk2 (3910763547447344963) -->
- <skip />
+ <string name="needPuk" msgid="3503414069503752211">"Your SIM is PUK-locked. Type the PUK code to unlock it."</string>
+ <string name="needPuk2" msgid="3910763547447344963">"Type PUK2 to unblock SIM."</string>
<string name="enablePin" msgid="2543771964137091212">"Unsuccessful, enable SIM/RUIM Lock."</string>
<plurals name="pinpuk_attempts" formatted="false" msgid="1619867269012213584">
<item quantity="other">You have <xliff:g id="NUMBER_1">%d</xliff:g> remaining attempts before SIM is locked.</item>
@@ -963,22 +961,14 @@
<string name="lockscreen_password_wrong" msgid="8605355913868947490">"Try again"</string>
<string name="lockscreen_storage_locked" msgid="634993789186443380">"Unlock for all features and data"</string>
<string name="faceunlock_multiple_failures" msgid="681991538434031708">"Maximum Face Unlock attempts exceeded"</string>
- <!-- no translation found for lockscreen_missing_sim_message_short (1229301273156907613) -->
- <skip />
- <!-- no translation found for lockscreen_missing_sim_message (3986843848305639161) -->
- <skip />
- <!-- no translation found for lockscreen_missing_sim_message (3903140876952198273) -->
- <skip />
- <!-- no translation found for lockscreen_missing_sim_message (6184187634180854181) -->
- <skip />
- <!-- no translation found for lockscreen_missing_sim_instructions (5823469004536805423) -->
- <skip />
- <!-- no translation found for lockscreen_missing_sim_instructions_long (4403843937236648032) -->
- <skip />
- <!-- no translation found for lockscreen_permanent_disabled_sim_message_short (1925200607820809677) -->
- <skip />
- <!-- no translation found for lockscreen_permanent_disabled_sim_instructions (6902979937802238429) -->
- <skip />
+ <string name="lockscreen_missing_sim_message_short" msgid="1229301273156907613">"No SIM"</string>
+ <string name="lockscreen_missing_sim_message" product="tablet" msgid="3986843848305639161">"No SIM in tablet."</string>
+ <string name="lockscreen_missing_sim_message" product="tv" msgid="3903140876952198273">"No SIM in your Android TV device."</string>
+ <string name="lockscreen_missing_sim_message" product="default" msgid="6184187634180854181">"No SIM in phone."</string>
+ <string name="lockscreen_missing_sim_instructions" msgid="5823469004536805423">"Add a SIM."</string>
+ <string name="lockscreen_missing_sim_instructions_long" msgid="4403843937236648032">"The SIM is missing or not readable. Add a SIM."</string>
+ <string name="lockscreen_permanent_disabled_sim_message_short" msgid="1925200607820809677">"Unusable SIM."</string>
+ <string name="lockscreen_permanent_disabled_sim_instructions" msgid="6902979937802238429">"Your SIM has been permanently deactivated.\n Contact your wireless service provider for another SIM."</string>
<string name="lockscreen_transport_prev_description" msgid="2879469521751181478">"Previous track"</string>
<string name="lockscreen_transport_next_description" msgid="2931509904881099919">"Next track"</string>
<string name="lockscreen_transport_pause_description" msgid="6705284702135372494">"Pause"</string>
@@ -988,13 +978,10 @@
<string name="lockscreen_transport_ffw_description" msgid="4763794746640196772">"Fast-forward"</string>
<string name="emergency_calls_only" msgid="3057351206678279851">"Emergency calls only"</string>
<string name="lockscreen_network_locked_message" msgid="2814046965899249635">"Network locked"</string>
- <!-- no translation found for lockscreen_sim_puk_locked_message (2867953953604224166) -->
- <skip />
+ <string name="lockscreen_sim_puk_locked_message" msgid="2867953953604224166">"SIM is PUK-locked."</string>
<string name="lockscreen_sim_puk_locked_instructions" msgid="5307979043730860995">"See the User Guide or contact Customer Care."</string>
- <!-- no translation found for lockscreen_sim_locked_message (5911944931911850164) -->
- <skip />
- <!-- no translation found for lockscreen_sim_unlock_progress_dialog_message (8381565919325410939) -->
- <skip />
+ <string name="lockscreen_sim_locked_message" msgid="5911944931911850164">"SIM is locked."</string>
+ <string name="lockscreen_sim_unlock_progress_dialog_message" msgid="8381565919325410939">"Unlocking SIM…"</string>
<string name="lockscreen_too_many_failed_attempts_dialog_message" msgid="6458790975898594240">"You have incorrectly drawn your unlock pattern <xliff:g id="NUMBER_0">%1$d</xliff:g> times. \n\nTry again in <xliff:g id="NUMBER_1">%2$d</xliff:g> seconds."</string>
<string name="lockscreen_too_many_failed_password_attempts_dialog_message" msgid="3118353451602377380">"You have incorrectly typed your password <xliff:g id="NUMBER_0">%1$d</xliff:g> times. \n\nTry again in <xliff:g id="NUMBER_1">%2$d</xliff:g> seconds."</string>
<string name="lockscreen_too_many_failed_pin_attempts_dialog_message" msgid="2874278239714821984">"You have incorrectly typed your PIN <xliff:g id="NUMBER_0">%1$d</xliff:g> times. \n\nTry again in <xliff:g id="NUMBER_1">%2$d</xliff:g> seconds."</string>
@@ -1378,13 +1365,10 @@
<string name="sms_short_code_remember_undo_instruction" msgid="2620984439143080410">"You can change this later in Settings > Apps"</string>
<string name="sms_short_code_confirm_always_allow" msgid="2223014893129755950">"Always Allow*"</string>
<string name="sms_short_code_confirm_never_allow" msgid="2688828813521652079">"Never Allow"</string>
- <!-- no translation found for sim_removed_title (1349026474932481037) -->
- <skip />
- <!-- no translation found for sim_removed_message (8469588437451533845) -->
- <skip />
+ <string name="sim_removed_title" msgid="1349026474932481037">"SIM removed"</string>
+ <string name="sim_removed_message" msgid="8469588437451533845">"The mobile network will be unavailable until you restart with a valid SIM."</string>
<string name="sim_done_button" msgid="6464250841528410598">"Done"</string>
- <!-- no translation found for sim_added_title (2976783426741012468) -->
- <skip />
+ <string name="sim_added_title" msgid="2976783426741012468">"SIM added"</string>
<string name="sim_added_message" msgid="6602906609509958680">"Restart your device to access the mobile network."</string>
<string name="sim_restart_button" msgid="8481803851341190038">"Restart"</string>
<string name="install_carrier_app_notification_title" msgid="5712723402213090102">"Activate mobile service"</string>
@@ -1696,8 +1680,7 @@
<string name="kg_puk_enter_puk_hint" msgid="6696187482616360994">"SIM is now disabled. Enter PUK code to continue. Contact operator for details."</string>
<string name="kg_puk_enter_pin_hint" msgid="8190982314659429770">"Enter desired PIN code"</string>
<string name="kg_enter_confirm_pin_hint" msgid="6372557107414074580">"Confirm desired PIN code"</string>
- <!-- no translation found for kg_sim_unlock_progress_dialog_message (5743634657721110967) -->
- <skip />
+ <string name="kg_sim_unlock_progress_dialog_message" msgid="5743634657721110967">"Unlocking SIM…"</string>
<string name="kg_password_wrong_pin_code" msgid="9013856346870572451">"Incorrect PIN code."</string>
<string name="kg_invalid_sim_pin_hint" msgid="4821601451222564077">"Type a PIN that is 4 to 8 numbers."</string>
<string name="kg_invalid_sim_puk_hint" msgid="2539364558870734339">"PUK code should be 8 numbers."</string>
@@ -1754,8 +1737,7 @@
<string name="disable_accessibility_shortcut" msgid="5806091378745232383">"Turn off Shortcut"</string>
<string name="leave_accessibility_shortcut_on" msgid="6543362062336990814">"Use Shortcut"</string>
<string name="color_inversion_feature_name" msgid="326050048927789012">"Colour Inversion"</string>
- <!-- no translation found for color_correction_feature_name (7975133554160979214) -->
- <skip />
+ <string name="color_correction_feature_name" msgid="7975133554160979214">"Colour correction"</string>
<string name="one_handed_mode_feature_name" msgid="2334330034828094891">"One-handed mode"</string>
<string name="reduce_bright_colors_feature_name" msgid="3222994553174604132">"Extra dim"</string>
<string name="accessibility_shortcut_enabling_service" msgid="5473495203759847687">"Held volume keys. <xliff:g id="SERVICE_NAME">%1$s</xliff:g> turned on."</string>
@@ -2352,4 +2334,8 @@
<string name="permdesc_deliverCompanionMessages" msgid="2170847384281412850">"Allows a companion app to deliver companion messages to other devices."</string>
<string name="permlab_startForegroundServicesFromBackground" msgid="6363004936218638382">"Start foreground services from background"</string>
<string name="permdesc_startForegroundServicesFromBackground" msgid="4071826571656001537">"Allows a companion app to start foreground services from background."</string>
+ <!-- no translation found for mic_access_on_toast (2666925317663845156) -->
+ <skip />
+ <!-- no translation found for mic_access_off_toast (8111040892954242437) -->
+ <skip />
</resources>
diff --git a/core/res/res/values-en-rCA/strings.xml b/core/res/res/values-en-rCA/strings.xml
index 71c366b..a2ffdb7 100644
--- a/core/res/res/values-en-rCA/strings.xml
+++ b/core/res/res/values-en-rCA/strings.xml
@@ -43,10 +43,8 @@
<string name="mismatchPin" msgid="2929611853228707473">"The PINs you typed don\'t match."</string>
<string name="invalidPin" msgid="7542498253319440408">"Type a PIN that is 4 to 8 numbers."</string>
<string name="invalidPuk" msgid="8831151490931907083">"Type a PUK that is 8 numbers or longer."</string>
- <!-- no translation found for needPuk (3503414069503752211) -->
- <skip />
- <!-- no translation found for needPuk2 (3910763547447344963) -->
- <skip />
+ <string name="needPuk" msgid="3503414069503752211">"Your SIM is PUK-locked. Type the PUK code to unlock it."</string>
+ <string name="needPuk2" msgid="3910763547447344963">"Type PUK2 to unblock SIM."</string>
<string name="enablePin" msgid="2543771964137091212">"Unsuccessful, enable SIM/RUIM Lock."</string>
<plurals name="pinpuk_attempts" formatted="false" msgid="1619867269012213584">
<item quantity="other">You have <xliff:g id="NUMBER_1">%d</xliff:g> remaining attempts before SIM is locked.</item>
@@ -963,22 +961,14 @@
<string name="lockscreen_password_wrong" msgid="8605355913868947490">"Try again"</string>
<string name="lockscreen_storage_locked" msgid="634993789186443380">"Unlock for all features and data"</string>
<string name="faceunlock_multiple_failures" msgid="681991538434031708">"Maximum Face Unlock attempts exceeded"</string>
- <!-- no translation found for lockscreen_missing_sim_message_short (1229301273156907613) -->
- <skip />
- <!-- no translation found for lockscreen_missing_sim_message (3986843848305639161) -->
- <skip />
- <!-- no translation found for lockscreen_missing_sim_message (3903140876952198273) -->
- <skip />
- <!-- no translation found for lockscreen_missing_sim_message (6184187634180854181) -->
- <skip />
- <!-- no translation found for lockscreen_missing_sim_instructions (5823469004536805423) -->
- <skip />
- <!-- no translation found for lockscreen_missing_sim_instructions_long (4403843937236648032) -->
- <skip />
- <!-- no translation found for lockscreen_permanent_disabled_sim_message_short (1925200607820809677) -->
- <skip />
- <!-- no translation found for lockscreen_permanent_disabled_sim_instructions (6902979937802238429) -->
- <skip />
+ <string name="lockscreen_missing_sim_message_short" msgid="1229301273156907613">"No SIM"</string>
+ <string name="lockscreen_missing_sim_message" product="tablet" msgid="3986843848305639161">"No SIM in tablet."</string>
+ <string name="lockscreen_missing_sim_message" product="tv" msgid="3903140876952198273">"No SIM in your Android TV device."</string>
+ <string name="lockscreen_missing_sim_message" product="default" msgid="6184187634180854181">"No SIM in phone."</string>
+ <string name="lockscreen_missing_sim_instructions" msgid="5823469004536805423">"Add a SIM."</string>
+ <string name="lockscreen_missing_sim_instructions_long" msgid="4403843937236648032">"The SIM is missing or not readable. Add a SIM."</string>
+ <string name="lockscreen_permanent_disabled_sim_message_short" msgid="1925200607820809677">"Unusable SIM."</string>
+ <string name="lockscreen_permanent_disabled_sim_instructions" msgid="6902979937802238429">"Your SIM has been permanently deactivated.\n Contact your wireless service provider for another SIM."</string>
<string name="lockscreen_transport_prev_description" msgid="2879469521751181478">"Previous track"</string>
<string name="lockscreen_transport_next_description" msgid="2931509904881099919">"Next track"</string>
<string name="lockscreen_transport_pause_description" msgid="6705284702135372494">"Pause"</string>
@@ -988,13 +978,10 @@
<string name="lockscreen_transport_ffw_description" msgid="4763794746640196772">"Fast forward"</string>
<string name="emergency_calls_only" msgid="3057351206678279851">"Emergency calls only"</string>
<string name="lockscreen_network_locked_message" msgid="2814046965899249635">"Network locked"</string>
- <!-- no translation found for lockscreen_sim_puk_locked_message (2867953953604224166) -->
- <skip />
+ <string name="lockscreen_sim_puk_locked_message" msgid="2867953953604224166">"SIM is PUK-locked."</string>
<string name="lockscreen_sim_puk_locked_instructions" msgid="5307979043730860995">"See the User Guide or contact Customer Care."</string>
- <!-- no translation found for lockscreen_sim_locked_message (5911944931911850164) -->
- <skip />
- <!-- no translation found for lockscreen_sim_unlock_progress_dialog_message (8381565919325410939) -->
- <skip />
+ <string name="lockscreen_sim_locked_message" msgid="5911944931911850164">"SIM is locked."</string>
+ <string name="lockscreen_sim_unlock_progress_dialog_message" msgid="8381565919325410939">"Unlocking SIM…"</string>
<string name="lockscreen_too_many_failed_attempts_dialog_message" msgid="6458790975898594240">"You have incorrectly drawn your unlock pattern <xliff:g id="NUMBER_0">%1$d</xliff:g> times. \n\nTry again in <xliff:g id="NUMBER_1">%2$d</xliff:g> seconds."</string>
<string name="lockscreen_too_many_failed_password_attempts_dialog_message" msgid="3118353451602377380">"You have incorrectly typed your password <xliff:g id="NUMBER_0">%1$d</xliff:g> times. \n\nTry again in <xliff:g id="NUMBER_1">%2$d</xliff:g> seconds."</string>
<string name="lockscreen_too_many_failed_pin_attempts_dialog_message" msgid="2874278239714821984">"You have incorrectly typed your PIN <xliff:g id="NUMBER_0">%1$d</xliff:g> times. \n\nTry again in <xliff:g id="NUMBER_1">%2$d</xliff:g> seconds."</string>
@@ -1378,13 +1365,10 @@
<string name="sms_short_code_remember_undo_instruction" msgid="2620984439143080410">"You can change this later in Settings > Apps"</string>
<string name="sms_short_code_confirm_always_allow" msgid="2223014893129755950">"Always allow"</string>
<string name="sms_short_code_confirm_never_allow" msgid="2688828813521652079">"Never Allow"</string>
- <!-- no translation found for sim_removed_title (1349026474932481037) -->
- <skip />
- <!-- no translation found for sim_removed_message (8469588437451533845) -->
- <skip />
+ <string name="sim_removed_title" msgid="1349026474932481037">"SIM removed"</string>
+ <string name="sim_removed_message" msgid="8469588437451533845">"The mobile network will be unavailable until you restart with a valid SIM."</string>
<string name="sim_done_button" msgid="6464250841528410598">"Done"</string>
- <!-- no translation found for sim_added_title (2976783426741012468) -->
- <skip />
+ <string name="sim_added_title" msgid="2976783426741012468">"SIM added"</string>
<string name="sim_added_message" msgid="6602906609509958680">"Restart your device to access the mobile network."</string>
<string name="sim_restart_button" msgid="8481803851341190038">"Restart"</string>
<string name="install_carrier_app_notification_title" msgid="5712723402213090102">"Activate mobile service"</string>
@@ -1696,8 +1680,7 @@
<string name="kg_puk_enter_puk_hint" msgid="6696187482616360994">"SIM is now disabled. Enter PUK code to continue. Contact carrier for details."</string>
<string name="kg_puk_enter_pin_hint" msgid="8190982314659429770">"Enter desired PIN code"</string>
<string name="kg_enter_confirm_pin_hint" msgid="6372557107414074580">"Confirm desired PIN code"</string>
- <!-- no translation found for kg_sim_unlock_progress_dialog_message (5743634657721110967) -->
- <skip />
+ <string name="kg_sim_unlock_progress_dialog_message" msgid="5743634657721110967">"Unlocking SIM…"</string>
<string name="kg_password_wrong_pin_code" msgid="9013856346870572451">"Incorrect PIN code."</string>
<string name="kg_invalid_sim_pin_hint" msgid="4821601451222564077">"Type a PIN that is 4 to 8 numbers."</string>
<string name="kg_invalid_sim_puk_hint" msgid="2539364558870734339">"PUK code should be 8 numbers."</string>
@@ -1754,8 +1737,7 @@
<string name="disable_accessibility_shortcut" msgid="5806091378745232383">"Turn off Shortcut"</string>
<string name="leave_accessibility_shortcut_on" msgid="6543362062336990814">"Use Shortcut"</string>
<string name="color_inversion_feature_name" msgid="326050048927789012">"Colour inversion"</string>
- <!-- no translation found for color_correction_feature_name (7975133554160979214) -->
- <skip />
+ <string name="color_correction_feature_name" msgid="7975133554160979214">"Color correction"</string>
<string name="one_handed_mode_feature_name" msgid="2334330034828094891">"One-Handed mode"</string>
<string name="reduce_bright_colors_feature_name" msgid="3222994553174604132">"Extra dim"</string>
<string name="accessibility_shortcut_enabling_service" msgid="5473495203759847687">"Held volume keys. <xliff:g id="SERVICE_NAME">%1$s</xliff:g> turned on."</string>
@@ -2352,4 +2334,8 @@
<string name="permdesc_deliverCompanionMessages" msgid="2170847384281412850">"Allows a companion app to deliver companion messages to other devices."</string>
<string name="permlab_startForegroundServicesFromBackground" msgid="6363004936218638382">"Start foreground services from background"</string>
<string name="permdesc_startForegroundServicesFromBackground" msgid="4071826571656001537">"Allows a companion app to start foreground services from background."</string>
+ <!-- no translation found for mic_access_on_toast (2666925317663845156) -->
+ <skip />
+ <!-- no translation found for mic_access_off_toast (8111040892954242437) -->
+ <skip />
</resources>
diff --git a/core/res/res/values-en-rGB/strings.xml b/core/res/res/values-en-rGB/strings.xml
index 1658c33..32774df 100644
--- a/core/res/res/values-en-rGB/strings.xml
+++ b/core/res/res/values-en-rGB/strings.xml
@@ -43,10 +43,8 @@
<string name="mismatchPin" msgid="2929611853228707473">"The PINs that you typed don\'t match."</string>
<string name="invalidPin" msgid="7542498253319440408">"Type a PIN that is 4 to 8 numbers."</string>
<string name="invalidPuk" msgid="8831151490931907083">"Type a PUK that is 8 numbers or longer."</string>
- <!-- no translation found for needPuk (3503414069503752211) -->
- <skip />
- <!-- no translation found for needPuk2 (3910763547447344963) -->
- <skip />
+ <string name="needPuk" msgid="3503414069503752211">"Your SIM is PUK-locked. Type the PUK code to unlock it."</string>
+ <string name="needPuk2" msgid="3910763547447344963">"Type PUK2 to unblock SIM."</string>
<string name="enablePin" msgid="2543771964137091212">"Unsuccessful, enable SIM/RUIM Lock."</string>
<plurals name="pinpuk_attempts" formatted="false" msgid="1619867269012213584">
<item quantity="other">You have <xliff:g id="NUMBER_1">%d</xliff:g> remaining attempts before SIM is locked.</item>
@@ -963,22 +961,14 @@
<string name="lockscreen_password_wrong" msgid="8605355913868947490">"Try again"</string>
<string name="lockscreen_storage_locked" msgid="634993789186443380">"Unlock for all features and data"</string>
<string name="faceunlock_multiple_failures" msgid="681991538434031708">"Maximum Face Unlock attempts exceeded"</string>
- <!-- no translation found for lockscreen_missing_sim_message_short (1229301273156907613) -->
- <skip />
- <!-- no translation found for lockscreen_missing_sim_message (3986843848305639161) -->
- <skip />
- <!-- no translation found for lockscreen_missing_sim_message (3903140876952198273) -->
- <skip />
- <!-- no translation found for lockscreen_missing_sim_message (6184187634180854181) -->
- <skip />
- <!-- no translation found for lockscreen_missing_sim_instructions (5823469004536805423) -->
- <skip />
- <!-- no translation found for lockscreen_missing_sim_instructions_long (4403843937236648032) -->
- <skip />
- <!-- no translation found for lockscreen_permanent_disabled_sim_message_short (1925200607820809677) -->
- <skip />
- <!-- no translation found for lockscreen_permanent_disabled_sim_instructions (6902979937802238429) -->
- <skip />
+ <string name="lockscreen_missing_sim_message_short" msgid="1229301273156907613">"No SIM"</string>
+ <string name="lockscreen_missing_sim_message" product="tablet" msgid="3986843848305639161">"No SIM in tablet."</string>
+ <string name="lockscreen_missing_sim_message" product="tv" msgid="3903140876952198273">"No SIM in your Android TV device."</string>
+ <string name="lockscreen_missing_sim_message" product="default" msgid="6184187634180854181">"No SIM in phone."</string>
+ <string name="lockscreen_missing_sim_instructions" msgid="5823469004536805423">"Add a SIM."</string>
+ <string name="lockscreen_missing_sim_instructions_long" msgid="4403843937236648032">"The SIM is missing or not readable. Add a SIM."</string>
+ <string name="lockscreen_permanent_disabled_sim_message_short" msgid="1925200607820809677">"Unusable SIM."</string>
+ <string name="lockscreen_permanent_disabled_sim_instructions" msgid="6902979937802238429">"Your SIM has been permanently deactivated.\n Contact your wireless service provider for another SIM."</string>
<string name="lockscreen_transport_prev_description" msgid="2879469521751181478">"Previous track"</string>
<string name="lockscreen_transport_next_description" msgid="2931509904881099919">"Next track"</string>
<string name="lockscreen_transport_pause_description" msgid="6705284702135372494">"Pause"</string>
@@ -988,13 +978,10 @@
<string name="lockscreen_transport_ffw_description" msgid="4763794746640196772">"Fast-forward"</string>
<string name="emergency_calls_only" msgid="3057351206678279851">"Emergency calls only"</string>
<string name="lockscreen_network_locked_message" msgid="2814046965899249635">"Network locked"</string>
- <!-- no translation found for lockscreen_sim_puk_locked_message (2867953953604224166) -->
- <skip />
+ <string name="lockscreen_sim_puk_locked_message" msgid="2867953953604224166">"SIM is PUK-locked."</string>
<string name="lockscreen_sim_puk_locked_instructions" msgid="5307979043730860995">"See the User Guide or contact Customer Care."</string>
- <!-- no translation found for lockscreen_sim_locked_message (5911944931911850164) -->
- <skip />
- <!-- no translation found for lockscreen_sim_unlock_progress_dialog_message (8381565919325410939) -->
- <skip />
+ <string name="lockscreen_sim_locked_message" msgid="5911944931911850164">"SIM is locked."</string>
+ <string name="lockscreen_sim_unlock_progress_dialog_message" msgid="8381565919325410939">"Unlocking SIM…"</string>
<string name="lockscreen_too_many_failed_attempts_dialog_message" msgid="6458790975898594240">"You have incorrectly drawn your unlock pattern <xliff:g id="NUMBER_0">%1$d</xliff:g> times. \n\nTry again in <xliff:g id="NUMBER_1">%2$d</xliff:g> seconds."</string>
<string name="lockscreen_too_many_failed_password_attempts_dialog_message" msgid="3118353451602377380">"You have incorrectly typed your password <xliff:g id="NUMBER_0">%1$d</xliff:g> times. \n\nTry again in <xliff:g id="NUMBER_1">%2$d</xliff:g> seconds."</string>
<string name="lockscreen_too_many_failed_pin_attempts_dialog_message" msgid="2874278239714821984">"You have incorrectly typed your PIN <xliff:g id="NUMBER_0">%1$d</xliff:g> times. \n\nTry again in <xliff:g id="NUMBER_1">%2$d</xliff:g> seconds."</string>
@@ -1378,13 +1365,10 @@
<string name="sms_short_code_remember_undo_instruction" msgid="2620984439143080410">"You can change this later in Settings > Apps"</string>
<string name="sms_short_code_confirm_always_allow" msgid="2223014893129755950">"Always Allow*"</string>
<string name="sms_short_code_confirm_never_allow" msgid="2688828813521652079">"Never Allow"</string>
- <!-- no translation found for sim_removed_title (1349026474932481037) -->
- <skip />
- <!-- no translation found for sim_removed_message (8469588437451533845) -->
- <skip />
+ <string name="sim_removed_title" msgid="1349026474932481037">"SIM removed"</string>
+ <string name="sim_removed_message" msgid="8469588437451533845">"The mobile network will be unavailable until you restart with a valid SIM."</string>
<string name="sim_done_button" msgid="6464250841528410598">"Done"</string>
- <!-- no translation found for sim_added_title (2976783426741012468) -->
- <skip />
+ <string name="sim_added_title" msgid="2976783426741012468">"SIM added"</string>
<string name="sim_added_message" msgid="6602906609509958680">"Restart your device to access the mobile network."</string>
<string name="sim_restart_button" msgid="8481803851341190038">"Restart"</string>
<string name="install_carrier_app_notification_title" msgid="5712723402213090102">"Activate mobile service"</string>
@@ -1696,8 +1680,7 @@
<string name="kg_puk_enter_puk_hint" msgid="6696187482616360994">"SIM is now disabled. Enter PUK code to continue. Contact operator for details."</string>
<string name="kg_puk_enter_pin_hint" msgid="8190982314659429770">"Enter desired PIN code"</string>
<string name="kg_enter_confirm_pin_hint" msgid="6372557107414074580">"Confirm desired PIN code"</string>
- <!-- no translation found for kg_sim_unlock_progress_dialog_message (5743634657721110967) -->
- <skip />
+ <string name="kg_sim_unlock_progress_dialog_message" msgid="5743634657721110967">"Unlocking SIM…"</string>
<string name="kg_password_wrong_pin_code" msgid="9013856346870572451">"Incorrect PIN code."</string>
<string name="kg_invalid_sim_pin_hint" msgid="4821601451222564077">"Type a PIN that is 4 to 8 numbers."</string>
<string name="kg_invalid_sim_puk_hint" msgid="2539364558870734339">"PUK code should be 8 numbers."</string>
@@ -1754,8 +1737,7 @@
<string name="disable_accessibility_shortcut" msgid="5806091378745232383">"Turn off Shortcut"</string>
<string name="leave_accessibility_shortcut_on" msgid="6543362062336990814">"Use Shortcut"</string>
<string name="color_inversion_feature_name" msgid="326050048927789012">"Colour Inversion"</string>
- <!-- no translation found for color_correction_feature_name (7975133554160979214) -->
- <skip />
+ <string name="color_correction_feature_name" msgid="7975133554160979214">"Colour correction"</string>
<string name="one_handed_mode_feature_name" msgid="2334330034828094891">"One-handed mode"</string>
<string name="reduce_bright_colors_feature_name" msgid="3222994553174604132">"Extra dim"</string>
<string name="accessibility_shortcut_enabling_service" msgid="5473495203759847687">"Held volume keys. <xliff:g id="SERVICE_NAME">%1$s</xliff:g> turned on."</string>
@@ -2352,4 +2334,8 @@
<string name="permdesc_deliverCompanionMessages" msgid="2170847384281412850">"Allows a companion app to deliver companion messages to other devices."</string>
<string name="permlab_startForegroundServicesFromBackground" msgid="6363004936218638382">"Start foreground services from background"</string>
<string name="permdesc_startForegroundServicesFromBackground" msgid="4071826571656001537">"Allows a companion app to start foreground services from background."</string>
+ <!-- no translation found for mic_access_on_toast (2666925317663845156) -->
+ <skip />
+ <!-- no translation found for mic_access_off_toast (8111040892954242437) -->
+ <skip />
</resources>
diff --git a/core/res/res/values-en-rIN/strings.xml b/core/res/res/values-en-rIN/strings.xml
index 182faa1..f742427 100644
--- a/core/res/res/values-en-rIN/strings.xml
+++ b/core/res/res/values-en-rIN/strings.xml
@@ -43,10 +43,8 @@
<string name="mismatchPin" msgid="2929611853228707473">"The PINs that you typed don\'t match."</string>
<string name="invalidPin" msgid="7542498253319440408">"Type a PIN that is 4 to 8 numbers."</string>
<string name="invalidPuk" msgid="8831151490931907083">"Type a PUK that is 8 numbers or longer."</string>
- <!-- no translation found for needPuk (3503414069503752211) -->
- <skip />
- <!-- no translation found for needPuk2 (3910763547447344963) -->
- <skip />
+ <string name="needPuk" msgid="3503414069503752211">"Your SIM is PUK-locked. Type the PUK code to unlock it."</string>
+ <string name="needPuk2" msgid="3910763547447344963">"Type PUK2 to unblock SIM."</string>
<string name="enablePin" msgid="2543771964137091212">"Unsuccessful, enable SIM/RUIM Lock."</string>
<plurals name="pinpuk_attempts" formatted="false" msgid="1619867269012213584">
<item quantity="other">You have <xliff:g id="NUMBER_1">%d</xliff:g> remaining attempts before SIM is locked.</item>
@@ -963,22 +961,14 @@
<string name="lockscreen_password_wrong" msgid="8605355913868947490">"Try again"</string>
<string name="lockscreen_storage_locked" msgid="634993789186443380">"Unlock for all features and data"</string>
<string name="faceunlock_multiple_failures" msgid="681991538434031708">"Maximum Face Unlock attempts exceeded"</string>
- <!-- no translation found for lockscreen_missing_sim_message_short (1229301273156907613) -->
- <skip />
- <!-- no translation found for lockscreen_missing_sim_message (3986843848305639161) -->
- <skip />
- <!-- no translation found for lockscreen_missing_sim_message (3903140876952198273) -->
- <skip />
- <!-- no translation found for lockscreen_missing_sim_message (6184187634180854181) -->
- <skip />
- <!-- no translation found for lockscreen_missing_sim_instructions (5823469004536805423) -->
- <skip />
- <!-- no translation found for lockscreen_missing_sim_instructions_long (4403843937236648032) -->
- <skip />
- <!-- no translation found for lockscreen_permanent_disabled_sim_message_short (1925200607820809677) -->
- <skip />
- <!-- no translation found for lockscreen_permanent_disabled_sim_instructions (6902979937802238429) -->
- <skip />
+ <string name="lockscreen_missing_sim_message_short" msgid="1229301273156907613">"No SIM"</string>
+ <string name="lockscreen_missing_sim_message" product="tablet" msgid="3986843848305639161">"No SIM in tablet."</string>
+ <string name="lockscreen_missing_sim_message" product="tv" msgid="3903140876952198273">"No SIM in your Android TV device."</string>
+ <string name="lockscreen_missing_sim_message" product="default" msgid="6184187634180854181">"No SIM in phone."</string>
+ <string name="lockscreen_missing_sim_instructions" msgid="5823469004536805423">"Add a SIM."</string>
+ <string name="lockscreen_missing_sim_instructions_long" msgid="4403843937236648032">"The SIM is missing or not readable. Add a SIM."</string>
+ <string name="lockscreen_permanent_disabled_sim_message_short" msgid="1925200607820809677">"Unusable SIM."</string>
+ <string name="lockscreen_permanent_disabled_sim_instructions" msgid="6902979937802238429">"Your SIM has been permanently deactivated.\n Contact your wireless service provider for another SIM."</string>
<string name="lockscreen_transport_prev_description" msgid="2879469521751181478">"Previous track"</string>
<string name="lockscreen_transport_next_description" msgid="2931509904881099919">"Next track"</string>
<string name="lockscreen_transport_pause_description" msgid="6705284702135372494">"Pause"</string>
@@ -988,13 +978,10 @@
<string name="lockscreen_transport_ffw_description" msgid="4763794746640196772">"Fast-forward"</string>
<string name="emergency_calls_only" msgid="3057351206678279851">"Emergency calls only"</string>
<string name="lockscreen_network_locked_message" msgid="2814046965899249635">"Network locked"</string>
- <!-- no translation found for lockscreen_sim_puk_locked_message (2867953953604224166) -->
- <skip />
+ <string name="lockscreen_sim_puk_locked_message" msgid="2867953953604224166">"SIM is PUK-locked."</string>
<string name="lockscreen_sim_puk_locked_instructions" msgid="5307979043730860995">"See the User Guide or contact Customer Care."</string>
- <!-- no translation found for lockscreen_sim_locked_message (5911944931911850164) -->
- <skip />
- <!-- no translation found for lockscreen_sim_unlock_progress_dialog_message (8381565919325410939) -->
- <skip />
+ <string name="lockscreen_sim_locked_message" msgid="5911944931911850164">"SIM is locked."</string>
+ <string name="lockscreen_sim_unlock_progress_dialog_message" msgid="8381565919325410939">"Unlocking SIM…"</string>
<string name="lockscreen_too_many_failed_attempts_dialog_message" msgid="6458790975898594240">"You have incorrectly drawn your unlock pattern <xliff:g id="NUMBER_0">%1$d</xliff:g> times. \n\nTry again in <xliff:g id="NUMBER_1">%2$d</xliff:g> seconds."</string>
<string name="lockscreen_too_many_failed_password_attempts_dialog_message" msgid="3118353451602377380">"You have incorrectly typed your password <xliff:g id="NUMBER_0">%1$d</xliff:g> times. \n\nTry again in <xliff:g id="NUMBER_1">%2$d</xliff:g> seconds."</string>
<string name="lockscreen_too_many_failed_pin_attempts_dialog_message" msgid="2874278239714821984">"You have incorrectly typed your PIN <xliff:g id="NUMBER_0">%1$d</xliff:g> times. \n\nTry again in <xliff:g id="NUMBER_1">%2$d</xliff:g> seconds."</string>
@@ -1378,13 +1365,10 @@
<string name="sms_short_code_remember_undo_instruction" msgid="2620984439143080410">"You can change this later in Settings > Apps"</string>
<string name="sms_short_code_confirm_always_allow" msgid="2223014893129755950">"Always Allow*"</string>
<string name="sms_short_code_confirm_never_allow" msgid="2688828813521652079">"Never Allow"</string>
- <!-- no translation found for sim_removed_title (1349026474932481037) -->
- <skip />
- <!-- no translation found for sim_removed_message (8469588437451533845) -->
- <skip />
+ <string name="sim_removed_title" msgid="1349026474932481037">"SIM removed"</string>
+ <string name="sim_removed_message" msgid="8469588437451533845">"The mobile network will be unavailable until you restart with a valid SIM."</string>
<string name="sim_done_button" msgid="6464250841528410598">"Done"</string>
- <!-- no translation found for sim_added_title (2976783426741012468) -->
- <skip />
+ <string name="sim_added_title" msgid="2976783426741012468">"SIM added"</string>
<string name="sim_added_message" msgid="6602906609509958680">"Restart your device to access the mobile network."</string>
<string name="sim_restart_button" msgid="8481803851341190038">"Restart"</string>
<string name="install_carrier_app_notification_title" msgid="5712723402213090102">"Activate mobile service"</string>
@@ -1696,8 +1680,7 @@
<string name="kg_puk_enter_puk_hint" msgid="6696187482616360994">"SIM is now disabled. Enter PUK code to continue. Contact operator for details."</string>
<string name="kg_puk_enter_pin_hint" msgid="8190982314659429770">"Enter desired PIN code"</string>
<string name="kg_enter_confirm_pin_hint" msgid="6372557107414074580">"Confirm desired PIN code"</string>
- <!-- no translation found for kg_sim_unlock_progress_dialog_message (5743634657721110967) -->
- <skip />
+ <string name="kg_sim_unlock_progress_dialog_message" msgid="5743634657721110967">"Unlocking SIM…"</string>
<string name="kg_password_wrong_pin_code" msgid="9013856346870572451">"Incorrect PIN code."</string>
<string name="kg_invalid_sim_pin_hint" msgid="4821601451222564077">"Type a PIN that is 4 to 8 numbers."</string>
<string name="kg_invalid_sim_puk_hint" msgid="2539364558870734339">"PUK code should be 8 numbers."</string>
@@ -1754,8 +1737,7 @@
<string name="disable_accessibility_shortcut" msgid="5806091378745232383">"Turn off Shortcut"</string>
<string name="leave_accessibility_shortcut_on" msgid="6543362062336990814">"Use Shortcut"</string>
<string name="color_inversion_feature_name" msgid="326050048927789012">"Colour Inversion"</string>
- <!-- no translation found for color_correction_feature_name (7975133554160979214) -->
- <skip />
+ <string name="color_correction_feature_name" msgid="7975133554160979214">"Colour correction"</string>
<string name="one_handed_mode_feature_name" msgid="2334330034828094891">"One-handed mode"</string>
<string name="reduce_bright_colors_feature_name" msgid="3222994553174604132">"Extra dim"</string>
<string name="accessibility_shortcut_enabling_service" msgid="5473495203759847687">"Held volume keys. <xliff:g id="SERVICE_NAME">%1$s</xliff:g> turned on."</string>
@@ -2352,4 +2334,8 @@
<string name="permdesc_deliverCompanionMessages" msgid="2170847384281412850">"Allows a companion app to deliver companion messages to other devices."</string>
<string name="permlab_startForegroundServicesFromBackground" msgid="6363004936218638382">"Start foreground services from background"</string>
<string name="permdesc_startForegroundServicesFromBackground" msgid="4071826571656001537">"Allows a companion app to start foreground services from background."</string>
+ <!-- no translation found for mic_access_on_toast (2666925317663845156) -->
+ <skip />
+ <!-- no translation found for mic_access_off_toast (8111040892954242437) -->
+ <skip />
</resources>
diff --git a/core/res/res/values-en-rXC/strings.xml b/core/res/res/values-en-rXC/strings.xml
index 39f9715..5a997be 100644
--- a/core/res/res/values-en-rXC/strings.xml
+++ b/core/res/res/values-en-rXC/strings.xml
@@ -43,10 +43,8 @@
<string name="mismatchPin" msgid="2929611853228707473">"The PINs you typed don\'t match."</string>
<string name="invalidPin" msgid="7542498253319440408">"Type a PIN that is 4 to 8 numbers."</string>
<string name="invalidPuk" msgid="8831151490931907083">"Type a PUK that is 8 numbers or longer."</string>
- <!-- no translation found for needPuk (3503414069503752211) -->
- <skip />
- <!-- no translation found for needPuk2 (3910763547447344963) -->
- <skip />
+ <string name="needPuk" msgid="3503414069503752211">"Your SIM is PUK-locked. Type the PUK code to unlock it."</string>
+ <string name="needPuk2" msgid="3910763547447344963">"Type PUK2 to unblock SIM."</string>
<string name="enablePin" msgid="2543771964137091212">"Unsuccessful, enable SIM/RUIM Lock."</string>
<plurals name="pinpuk_attempts" formatted="false" msgid="1619867269012213584">
<item quantity="other">You have <xliff:g id="NUMBER_1">%d</xliff:g> remaining attempts before SIM is locked.</item>
@@ -963,22 +961,14 @@
<string name="lockscreen_password_wrong" msgid="8605355913868947490">"Try again"</string>
<string name="lockscreen_storage_locked" msgid="634993789186443380">"Unlock for all features and data"</string>
<string name="faceunlock_multiple_failures" msgid="681991538434031708">"Maximum Face Unlock attempts exceeded"</string>
- <!-- no translation found for lockscreen_missing_sim_message_short (1229301273156907613) -->
- <skip />
- <!-- no translation found for lockscreen_missing_sim_message (3986843848305639161) -->
- <skip />
- <!-- no translation found for lockscreen_missing_sim_message (3903140876952198273) -->
- <skip />
- <!-- no translation found for lockscreen_missing_sim_message (6184187634180854181) -->
- <skip />
- <!-- no translation found for lockscreen_missing_sim_instructions (5823469004536805423) -->
- <skip />
- <!-- no translation found for lockscreen_missing_sim_instructions_long (4403843937236648032) -->
- <skip />
- <!-- no translation found for lockscreen_permanent_disabled_sim_message_short (1925200607820809677) -->
- <skip />
- <!-- no translation found for lockscreen_permanent_disabled_sim_instructions (6902979937802238429) -->
- <skip />
+ <string name="lockscreen_missing_sim_message_short" msgid="1229301273156907613">"No SIM"</string>
+ <string name="lockscreen_missing_sim_message" product="tablet" msgid="3986843848305639161">"No SIM in tablet."</string>
+ <string name="lockscreen_missing_sim_message" product="tv" msgid="3903140876952198273">"No SIM in your Android TV device."</string>
+ <string name="lockscreen_missing_sim_message" product="default" msgid="6184187634180854181">"No SIM in phone."</string>
+ <string name="lockscreen_missing_sim_instructions" msgid="5823469004536805423">"Add a SIM."</string>
+ <string name="lockscreen_missing_sim_instructions_long" msgid="4403843937236648032">"The SIM is missing or not readable. Add a SIM."</string>
+ <string name="lockscreen_permanent_disabled_sim_message_short" msgid="1925200607820809677">"Unusable SIM."</string>
+ <string name="lockscreen_permanent_disabled_sim_instructions" msgid="6902979937802238429">"Your SIM has been permanently deactivated.\n Contact your wireless service provider for another SIM."</string>
<string name="lockscreen_transport_prev_description" msgid="2879469521751181478">"Previous track"</string>
<string name="lockscreen_transport_next_description" msgid="2931509904881099919">"Next track"</string>
<string name="lockscreen_transport_pause_description" msgid="6705284702135372494">"Pause"</string>
@@ -988,13 +978,10 @@
<string name="lockscreen_transport_ffw_description" msgid="4763794746640196772">"Fast forward"</string>
<string name="emergency_calls_only" msgid="3057351206678279851">"Emergency calls only"</string>
<string name="lockscreen_network_locked_message" msgid="2814046965899249635">"Network locked"</string>
- <!-- no translation found for lockscreen_sim_puk_locked_message (2867953953604224166) -->
- <skip />
+ <string name="lockscreen_sim_puk_locked_message" msgid="2867953953604224166">"SIM is PUK-locked."</string>
<string name="lockscreen_sim_puk_locked_instructions" msgid="5307979043730860995">"See the User Guide or contact Customer Care."</string>
- <!-- no translation found for lockscreen_sim_locked_message (5911944931911850164) -->
- <skip />
- <!-- no translation found for lockscreen_sim_unlock_progress_dialog_message (8381565919325410939) -->
- <skip />
+ <string name="lockscreen_sim_locked_message" msgid="5911944931911850164">"SIM is locked."</string>
+ <string name="lockscreen_sim_unlock_progress_dialog_message" msgid="8381565919325410939">"Unlocking SIM…"</string>
<string name="lockscreen_too_many_failed_attempts_dialog_message" msgid="6458790975898594240">"You have incorrectly drawn your unlock pattern <xliff:g id="NUMBER_0">%1$d</xliff:g> times. \n\nTry again in <xliff:g id="NUMBER_1">%2$d</xliff:g> seconds."</string>
<string name="lockscreen_too_many_failed_password_attempts_dialog_message" msgid="3118353451602377380">"You have incorrectly typed your password <xliff:g id="NUMBER_0">%1$d</xliff:g> times. \n\nTry again in <xliff:g id="NUMBER_1">%2$d</xliff:g> seconds."</string>
<string name="lockscreen_too_many_failed_pin_attempts_dialog_message" msgid="2874278239714821984">"You have incorrectly typed your PIN <xliff:g id="NUMBER_0">%1$d</xliff:g> times. \n\nTry again in <xliff:g id="NUMBER_1">%2$d</xliff:g> seconds."</string>
@@ -1378,13 +1365,10 @@
<string name="sms_short_code_remember_undo_instruction" msgid="2620984439143080410">"You can change this later in Settings > Apps"</string>
<string name="sms_short_code_confirm_always_allow" msgid="2223014893129755950">"Always Allow"</string>
<string name="sms_short_code_confirm_never_allow" msgid="2688828813521652079">"Never Allow"</string>
- <!-- no translation found for sim_removed_title (1349026474932481037) -->
- <skip />
- <!-- no translation found for sim_removed_message (8469588437451533845) -->
- <skip />
+ <string name="sim_removed_title" msgid="1349026474932481037">"SIM removed"</string>
+ <string name="sim_removed_message" msgid="8469588437451533845">"The mobile network will be unavailable until you restart with a valid SIM."</string>
<string name="sim_done_button" msgid="6464250841528410598">"Done"</string>
- <!-- no translation found for sim_added_title (2976783426741012468) -->
- <skip />
+ <string name="sim_added_title" msgid="2976783426741012468">"SIM added"</string>
<string name="sim_added_message" msgid="6602906609509958680">"Restart your device to access the mobile network."</string>
<string name="sim_restart_button" msgid="8481803851341190038">"Restart"</string>
<string name="install_carrier_app_notification_title" msgid="5712723402213090102">"Activate mobile service"</string>
@@ -1696,8 +1680,7 @@
<string name="kg_puk_enter_puk_hint" msgid="6696187482616360994">"SIM is now disabled. Enter PUK code to continue. Contact carrier for details."</string>
<string name="kg_puk_enter_pin_hint" msgid="8190982314659429770">"Enter desired PIN code"</string>
<string name="kg_enter_confirm_pin_hint" msgid="6372557107414074580">"Confirm desired PIN code"</string>
- <!-- no translation found for kg_sim_unlock_progress_dialog_message (5743634657721110967) -->
- <skip />
+ <string name="kg_sim_unlock_progress_dialog_message" msgid="5743634657721110967">"Unlocking SIM…"</string>
<string name="kg_password_wrong_pin_code" msgid="9013856346870572451">"Incorrect PIN code."</string>
<string name="kg_invalid_sim_pin_hint" msgid="4821601451222564077">"Type a PIN that is 4 to 8 numbers."</string>
<string name="kg_invalid_sim_puk_hint" msgid="2539364558870734339">"PUK code should be 8 numbers."</string>
@@ -1754,8 +1737,7 @@
<string name="disable_accessibility_shortcut" msgid="5806091378745232383">"Turn off Shortcut"</string>
<string name="leave_accessibility_shortcut_on" msgid="6543362062336990814">"Use Shortcut"</string>
<string name="color_inversion_feature_name" msgid="326050048927789012">"Color Inversion"</string>
- <!-- no translation found for color_correction_feature_name (7975133554160979214) -->
- <skip />
+ <string name="color_correction_feature_name" msgid="7975133554160979214">"Color correction"</string>
<string name="one_handed_mode_feature_name" msgid="2334330034828094891">"One-Handed mode"</string>
<string name="reduce_bright_colors_feature_name" msgid="3222994553174604132">"Extra dim"</string>
<string name="accessibility_shortcut_enabling_service" msgid="5473495203759847687">"Held volume keys. <xliff:g id="SERVICE_NAME">%1$s</xliff:g> turned on."</string>
@@ -2352,4 +2334,8 @@
<string name="permdesc_deliverCompanionMessages" msgid="2170847384281412850">"Allows a companion app to deliver companion messages to other devices."</string>
<string name="permlab_startForegroundServicesFromBackground" msgid="6363004936218638382">"Start foreground services from background"</string>
<string name="permdesc_startForegroundServicesFromBackground" msgid="4071826571656001537">"Allows a companion app to start foreground services from background."</string>
+ <!-- no translation found for mic_access_on_toast (2666925317663845156) -->
+ <skip />
+ <!-- no translation found for mic_access_off_toast (8111040892954242437) -->
+ <skip />
</resources>
diff --git a/core/res/res/values-es-rUS/strings.xml b/core/res/res/values-es-rUS/strings.xml
index 8608754..52053e8 100644
--- a/core/res/res/values-es-rUS/strings.xml
+++ b/core/res/res/values-es-rUS/strings.xml
@@ -2353,4 +2353,8 @@
<string name="permdesc_deliverCompanionMessages" msgid="2170847384281412850">"Permite que una aplicación complementaria envíe mensajes complementarios a otros dispositivos."</string>
<string name="permlab_startForegroundServicesFromBackground" msgid="6363004936218638382">"Comenzar servicios en primer plano desde el segundo plano"</string>
<string name="permdesc_startForegroundServicesFromBackground" msgid="4071826571656001537">"Permite que una aplicación complementaria inicie servicios en primer plano desde el segundo plano."</string>
+ <!-- no translation found for mic_access_on_toast (2666925317663845156) -->
+ <skip />
+ <!-- no translation found for mic_access_off_toast (8111040892954242437) -->
+ <skip />
</resources>
diff --git a/core/res/res/values-es/strings.xml b/core/res/res/values-es/strings.xml
index 86837362..639ef0b 100644
--- a/core/res/res/values-es/strings.xml
+++ b/core/res/res/values-es/strings.xml
@@ -2353,4 +2353,8 @@
<string name="permdesc_deliverCompanionMessages" msgid="2170847384281412850">"Permite que una aplicación complementaria envíe mensajes complementarios a otros dispositivos."</string>
<string name="permlab_startForegroundServicesFromBackground" msgid="6363004936218638382">"Iniciar servicios en primer plano desde el segundo plano"</string>
<string name="permdesc_startForegroundServicesFromBackground" msgid="4071826571656001537">"Permite que una aplicación complementaria inicie servicios en primer plano desde el segundo plano."</string>
+ <!-- no translation found for mic_access_on_toast (2666925317663845156) -->
+ <skip />
+ <!-- no translation found for mic_access_off_toast (8111040892954242437) -->
+ <skip />
</resources>
diff --git a/core/res/res/values-et/strings.xml b/core/res/res/values-et/strings.xml
index d469e40..210b509 100644
--- a/core/res/res/values-et/strings.xml
+++ b/core/res/res/values-et/strings.xml
@@ -2352,4 +2352,8 @@
<string name="permdesc_deliverCompanionMessages" msgid="2170847384281412850">"Lubab kaasrakendusel teistesse seadmetesse kaassõnumeid toimetada."</string>
<string name="permlab_startForegroundServicesFromBackground" msgid="6363004936218638382">"Esiplaanil olevate teenuste käivitamine taustal"</string>
<string name="permdesc_startForegroundServicesFromBackground" msgid="4071826571656001537">"Lubab kaasrakendusel taustal käivitada esiplaanil olevaid teenuseid."</string>
+ <!-- no translation found for mic_access_on_toast (2666925317663845156) -->
+ <skip />
+ <!-- no translation found for mic_access_off_toast (8111040892954242437) -->
+ <skip />
</resources>
diff --git a/core/res/res/values-eu/strings.xml b/core/res/res/values-eu/strings.xml
index ee30c2d..c60da1d 100644
--- a/core/res/res/values-eu/strings.xml
+++ b/core/res/res/values-eu/strings.xml
@@ -2352,4 +2352,8 @@
<string name="permdesc_deliverCompanionMessages" msgid="2170847384281412850">"Beste gailuetan mezuak entregatzeko baimena ematen die aplikazio osagarriei."</string>
<string name="permlab_startForegroundServicesFromBackground" msgid="6363004936218638382">"hasi aurreko planoko zerbitzuak atzeko planotik"</string>
<string name="permdesc_startForegroundServicesFromBackground" msgid="4071826571656001537">"Aurreko planoko zerbitzuak atzeko planotik abiarazteko baimena ematen die aplikazio osagarriei."</string>
+ <!-- no translation found for mic_access_on_toast (2666925317663845156) -->
+ <skip />
+ <!-- no translation found for mic_access_off_toast (8111040892954242437) -->
+ <skip />
</resources>
diff --git a/core/res/res/values-fa/strings.xml b/core/res/res/values-fa/strings.xml
index ed5a2ac..670ab45 100644
--- a/core/res/res/values-fa/strings.xml
+++ b/core/res/res/values-fa/strings.xml
@@ -2352,4 +2352,8 @@
<string name="permdesc_deliverCompanionMessages" msgid="2170847384281412850">"به برنامه همراه اجازه میدهد پیامهای همراه را به دستگاههای دیگر ارسال کند."</string>
<string name="permlab_startForegroundServicesFromBackground" msgid="6363004936218638382">"اجرای سرویسهای پیشنما از پسزمینه"</string>
<string name="permdesc_startForegroundServicesFromBackground" msgid="4071826571656001537">"به برنامه همراه اجازه میدهد سرویسهای پیشنما را از پسزمینه راهاندازی کند."</string>
+ <!-- no translation found for mic_access_on_toast (2666925317663845156) -->
+ <skip />
+ <!-- no translation found for mic_access_off_toast (8111040892954242437) -->
+ <skip />
</resources>
diff --git a/core/res/res/values-fi/strings.xml b/core/res/res/values-fi/strings.xml
index c1bf4c6..df6f15b 100644
--- a/core/res/res/values-fi/strings.xml
+++ b/core/res/res/values-fi/strings.xml
@@ -2352,4 +2352,8 @@
<string name="permdesc_deliverCompanionMessages" msgid="2170847384281412850">"Sallii kumppanisovelluksen toimittaa kumppaniviestejä muille laitteille."</string>
<string name="permlab_startForegroundServicesFromBackground" msgid="6363004936218638382">"Etualan palvelujen aloittaminen taustalla"</string>
<string name="permdesc_startForegroundServicesFromBackground" msgid="4071826571656001537">"Sallii kumppanisovelluksen aloittaa etualan palveluja taustalla."</string>
+ <!-- no translation found for mic_access_on_toast (2666925317663845156) -->
+ <skip />
+ <!-- no translation found for mic_access_off_toast (8111040892954242437) -->
+ <skip />
</resources>
diff --git a/core/res/res/values-fr-rCA/strings.xml b/core/res/res/values-fr-rCA/strings.xml
index bc2130c..0a7ce08 100644
--- a/core/res/res/values-fr-rCA/strings.xml
+++ b/core/res/res/values-fr-rCA/strings.xml
@@ -2353,4 +2353,8 @@
<string name="permdesc_deliverCompanionMessages" msgid="2170847384281412850">"Autorise une application compagnon à transmettre des messages à d\'autres appareils."</string>
<string name="permlab_startForegroundServicesFromBackground" msgid="6363004936218638382">"Lancer les services d\'avant-plan à partir de l\'arrière-plan"</string>
<string name="permdesc_startForegroundServicesFromBackground" msgid="4071826571656001537">"Permet à une application compagnon en arrière-plan de lancer des services d\'avant-plan."</string>
+ <!-- no translation found for mic_access_on_toast (2666925317663845156) -->
+ <skip />
+ <!-- no translation found for mic_access_off_toast (8111040892954242437) -->
+ <skip />
</resources>
diff --git a/core/res/res/values-fr/strings.xml b/core/res/res/values-fr/strings.xml
index 0e76f98..bb449b0 100644
--- a/core/res/res/values-fr/strings.xml
+++ b/core/res/res/values-fr/strings.xml
@@ -2353,4 +2353,8 @@
<string name="permdesc_deliverCompanionMessages" msgid="2170847384281412850">"Autorise une application associée à transmettre des messages associés à d\'autres appareils."</string>
<string name="permlab_startForegroundServicesFromBackground" msgid="6363004936218638382">"Lancer des services de premier plan à partir de l\'arrière-plan"</string>
<string name="permdesc_startForegroundServicesFromBackground" msgid="4071826571656001537">"Autorise une application associée à lancer des services de premier plan à partir de l\'arrière-plan."</string>
+ <!-- no translation found for mic_access_on_toast (2666925317663845156) -->
+ <skip />
+ <!-- no translation found for mic_access_off_toast (8111040892954242437) -->
+ <skip />
</resources>
diff --git a/core/res/res/values-gl/strings.xml b/core/res/res/values-gl/strings.xml
index 7aeeea0..7628658 100644
--- a/core/res/res/values-gl/strings.xml
+++ b/core/res/res/values-gl/strings.xml
@@ -2352,4 +2352,8 @@
<string name="permdesc_deliverCompanionMessages" msgid="2170847384281412850">"Permite que unha aplicación complementaria envíe mensaxes complementarias a outros dispositivos."</string>
<string name="permlab_startForegroundServicesFromBackground" msgid="6363004936218638382">"Desde un segundo plano iniciar servizos en primeiro plano"</string>
<string name="permdesc_startForegroundServicesFromBackground" msgid="4071826571656001537">"Permite que nun segundo plano unha aplicación complementaria inicie servizos en primeiro plano."</string>
+ <!-- no translation found for mic_access_on_toast (2666925317663845156) -->
+ <skip />
+ <!-- no translation found for mic_access_off_toast (8111040892954242437) -->
+ <skip />
</resources>
diff --git a/core/res/res/values-gu/strings.xml b/core/res/res/values-gu/strings.xml
index de3f8ae..4cae4e5 100644
--- a/core/res/res/values-gu/strings.xml
+++ b/core/res/res/values-gu/strings.xml
@@ -2352,4 +2352,8 @@
<string name="permdesc_deliverCompanionMessages" msgid="2170847384281412850">"અન્ય ડિવાઇસ પર સાથી મેસેજ ડિલિવર કરવા માટે સાથી ઍપને મંજૂરી આપે છે."</string>
<string name="permlab_startForegroundServicesFromBackground" msgid="6363004936218638382">"બૅકગ્રાઉન્ડમાંથી ફૉરગ્રાઉન્ડ સેવાઓ શરૂ કરો"</string>
<string name="permdesc_startForegroundServicesFromBackground" msgid="4071826571656001537">"સાથી ઍપને બૅકગ્રાઉન્ડમાંથી ફૉરગ્રાઉન્ડ સેવાઓ શરૂ કરવાની મંજૂરી આપે છે."</string>
+ <!-- no translation found for mic_access_on_toast (2666925317663845156) -->
+ <skip />
+ <!-- no translation found for mic_access_off_toast (8111040892954242437) -->
+ <skip />
</resources>
diff --git a/core/res/res/values-hi/strings.xml b/core/res/res/values-hi/strings.xml
index 31518e9..fb4af5d 100644
--- a/core/res/res/values-hi/strings.xml
+++ b/core/res/res/values-hi/strings.xml
@@ -2352,4 +2352,8 @@
<string name="permdesc_deliverCompanionMessages" msgid="2170847384281412850">"इससे साथी ऐप्लिकेशन को अन्य डिवाइसों पर, साथी ऐप्लिकेशन के मैसेज डिलीवर करने की अनुमति मिलती है."</string>
<string name="permlab_startForegroundServicesFromBackground" msgid="6363004936218638382">"बैकग्राउंड में फ़ोरग्राउंड सेवाएं चलाने की अनुमति दें"</string>
<string name="permdesc_startForegroundServicesFromBackground" msgid="4071826571656001537">"इससे साथी ऐप्लिकेशन को बैकग्राउंड में फ़ोरग्राउंड सेवाएं चलाने की अनुमति मिलती है."</string>
+ <!-- no translation found for mic_access_on_toast (2666925317663845156) -->
+ <skip />
+ <!-- no translation found for mic_access_off_toast (8111040892954242437) -->
+ <skip />
</resources>
diff --git a/core/res/res/values-hr/strings.xml b/core/res/res/values-hr/strings.xml
index e057dd3..02338fb 100644
--- a/core/res/res/values-hr/strings.xml
+++ b/core/res/res/values-hr/strings.xml
@@ -2353,4 +2353,8 @@
<string name="permdesc_deliverCompanionMessages" msgid="2170847384281412850">"Popratnoj aplikaciji omogućuje isporuku popratnih poruka drugim uređajima."</string>
<string name="permlab_startForegroundServicesFromBackground" msgid="6363004936218638382">"Pokreni usluge u prednjem planu iz pozadine"</string>
<string name="permdesc_startForegroundServicesFromBackground" msgid="4071826571656001537">"Popratnoj aplikaciji omogućuje da iz pozadine pokrene usluge u prednjem planu."</string>
+ <!-- no translation found for mic_access_on_toast (2666925317663845156) -->
+ <skip />
+ <!-- no translation found for mic_access_off_toast (8111040892954242437) -->
+ <skip />
</resources>
diff --git a/core/res/res/values-hu/strings.xml b/core/res/res/values-hu/strings.xml
index 1d9da72..cb497f4 100644
--- a/core/res/res/values-hu/strings.xml
+++ b/core/res/res/values-hu/strings.xml
@@ -2352,4 +2352,8 @@
<string name="permdesc_deliverCompanionMessages" msgid="2170847384281412850">"Lehetővé teszi a társalkalmazások számára, hogy társüzeneteket küldjenek más eszközökre."</string>
<string name="permlab_startForegroundServicesFromBackground" msgid="6363004936218638382">"Előtérben futó szolgáltatások indítása a háttérből"</string>
<string name="permdesc_startForegroundServicesFromBackground" msgid="4071826571656001537">"Lehetővé teszi a társalkalmazások számára, hogy előtérben futó szolgáltatásokat indítsanak a háttérből."</string>
+ <!-- no translation found for mic_access_on_toast (2666925317663845156) -->
+ <skip />
+ <!-- no translation found for mic_access_off_toast (8111040892954242437) -->
+ <skip />
</resources>
diff --git a/core/res/res/values-hy/strings.xml b/core/res/res/values-hy/strings.xml
index bf2120a..89f6027 100644
--- a/core/res/res/values-hy/strings.xml
+++ b/core/res/res/values-hy/strings.xml
@@ -2352,4 +2352,8 @@
<string name="permdesc_deliverCompanionMessages" msgid="2170847384281412850">"Թույլատրում է ուղեկցող հավելվածին հաղորդագրություններ առաքել այլ սարքեր։"</string>
<string name="permlab_startForegroundServicesFromBackground" msgid="6363004936218638382">"Ակտիվ ծառայությունների գործարկում ֆոնային ռեժիմից"</string>
<string name="permdesc_startForegroundServicesFromBackground" msgid="4071826571656001537">"Թույլատրում է ուղեկցող հավելվածին ակտիվ ծառայություններ գործարկել ֆոնային ռեժիմից։"</string>
+ <!-- no translation found for mic_access_on_toast (2666925317663845156) -->
+ <skip />
+ <!-- no translation found for mic_access_off_toast (8111040892954242437) -->
+ <skip />
</resources>
diff --git a/core/res/res/values-in/strings.xml b/core/res/res/values-in/strings.xml
index d4046eb..b3bece9 100644
--- a/core/res/res/values-in/strings.xml
+++ b/core/res/res/values-in/strings.xml
@@ -2352,4 +2352,8 @@
<string name="permdesc_deliverCompanionMessages" msgid="2170847384281412850">"Mengizinkan aplikasi pendamping mengirimkan pesan pendamping ke perangkat lain."</string>
<string name="permlab_startForegroundServicesFromBackground" msgid="6363004936218638382">"Memulai layanan latar depan dari latar belakang"</string>
<string name="permdesc_startForegroundServicesFromBackground" msgid="4071826571656001537">"Mengizinkan aplikasi pendamping memulai layanan latar depan dari latar belakang."</string>
+ <!-- no translation found for mic_access_on_toast (2666925317663845156) -->
+ <skip />
+ <!-- no translation found for mic_access_off_toast (8111040892954242437) -->
+ <skip />
</resources>
diff --git a/core/res/res/values-is/strings.xml b/core/res/res/values-is/strings.xml
index 7c6e719..4e44ba5 100644
--- a/core/res/res/values-is/strings.xml
+++ b/core/res/res/values-is/strings.xml
@@ -2352,4 +2352,8 @@
<string name="permdesc_deliverCompanionMessages" msgid="2170847384281412850">"Leyfir fylgiforriti að senda fylgiskilaboð til annarra tækja."</string>
<string name="permlab_startForegroundServicesFromBackground" msgid="6363004936218638382">"Ræsa forgrunnsþjónustur úr bakgrunni"</string>
<string name="permdesc_startForegroundServicesFromBackground" msgid="4071826571656001537">"Leyfir fylgiforriti að ræsa forgrunnsþjónustur úr bakgrunni."</string>
+ <!-- no translation found for mic_access_on_toast (2666925317663845156) -->
+ <skip />
+ <!-- no translation found for mic_access_off_toast (8111040892954242437) -->
+ <skip />
</resources>
diff --git a/core/res/res/values-it/strings.xml b/core/res/res/values-it/strings.xml
index a4e0c17..ee704ab 100644
--- a/core/res/res/values-it/strings.xml
+++ b/core/res/res/values-it/strings.xml
@@ -2353,4 +2353,8 @@
<string name="permdesc_deliverCompanionMessages" msgid="2170847384281412850">"Consente a un\'app complementare di consegnare messaggi complementari ad altri dispositivi."</string>
<string name="permlab_startForegroundServicesFromBackground" msgid="6363004936218638382">"Avviare i servizi in primo piano dal background"</string>
<string name="permdesc_startForegroundServicesFromBackground" msgid="4071826571656001537">"Consente a un\'app complementare di avviare servizi in primo piano dal background."</string>
+ <!-- no translation found for mic_access_on_toast (2666925317663845156) -->
+ <skip />
+ <!-- no translation found for mic_access_off_toast (8111040892954242437) -->
+ <skip />
</resources>
diff --git a/core/res/res/values-iw/strings.xml b/core/res/res/values-iw/strings.xml
index dda48df..3480e98 100644
--- a/core/res/res/values-iw/strings.xml
+++ b/core/res/res/values-iw/strings.xml
@@ -2353,4 +2353,8 @@
<string name="permdesc_deliverCompanionMessages" msgid="2170847384281412850">"ההרשאה הזו מאפשרת לאפליקציה נלווית להעביר הודעות נלוות למכשירים אחרים."</string>
<string name="permlab_startForegroundServicesFromBackground" msgid="6363004936218638382">"הפעלה מהרקע של שירותים שפועלים בחזית"</string>
<string name="permdesc_startForegroundServicesFromBackground" msgid="4071826571656001537">"ההרשאה הזו מאפשרת לאפליקציה נלווית להפעיל מהרקע שירותים שפועלים בחזית."</string>
+ <!-- no translation found for mic_access_on_toast (2666925317663845156) -->
+ <skip />
+ <!-- no translation found for mic_access_off_toast (8111040892954242437) -->
+ <skip />
</resources>
diff --git a/core/res/res/values-ja/strings.xml b/core/res/res/values-ja/strings.xml
index 0bd593b..d626dae 100644
--- a/core/res/res/values-ja/strings.xml
+++ b/core/res/res/values-ja/strings.xml
@@ -2352,4 +2352,8 @@
<string name="permdesc_deliverCompanionMessages" msgid="2170847384281412850">"他のデバイスへのコンパニオン メッセージの配信をコンパニオン アプリに許可します。"</string>
<string name="permlab_startForegroundServicesFromBackground" msgid="6363004936218638382">"バックグラウンドからのフォアグラウンド サービスの起動"</string>
<string name="permdesc_startForegroundServicesFromBackground" msgid="4071826571656001537">"バックグラウンドからのフォアグラウンド サービスの起動をコンパニオン アプリに許可します。"</string>
+ <!-- no translation found for mic_access_on_toast (2666925317663845156) -->
+ <skip />
+ <!-- no translation found for mic_access_off_toast (8111040892954242437) -->
+ <skip />
</resources>
diff --git a/core/res/res/values-ka/strings.xml b/core/res/res/values-ka/strings.xml
index ea000b2..5af4fb4 100644
--- a/core/res/res/values-ka/strings.xml
+++ b/core/res/res/values-ka/strings.xml
@@ -2352,4 +2352,8 @@
<string name="permdesc_deliverCompanionMessages" msgid="2170847384281412850">"კომპანიონ აპს საშუალებას აძლევს, რომ მიაწოდოს გზავნილები სხვა მოწყობილობებზე."</string>
<string name="permlab_startForegroundServicesFromBackground" msgid="6363004936218638382">"უპირატესი სერვისების ფონური რეჟიმიდან გაშვება"</string>
<string name="permdesc_startForegroundServicesFromBackground" msgid="4071826571656001537">"საშუალებას აძლევს კომპანიონ აპს, რომ გაუშვას უპირატესი სერვისები ფონური რეჟიმიდან."</string>
+ <!-- no translation found for mic_access_on_toast (2666925317663845156) -->
+ <skip />
+ <!-- no translation found for mic_access_off_toast (8111040892954242437) -->
+ <skip />
</resources>
diff --git a/core/res/res/values-kk/strings.xml b/core/res/res/values-kk/strings.xml
index 8ffd05e..6c2245c 100644
--- a/core/res/res/values-kk/strings.xml
+++ b/core/res/res/values-kk/strings.xml
@@ -2352,4 +2352,8 @@
<string name="permdesc_deliverCompanionMessages" msgid="2170847384281412850">"Қосымша қолданбаға ілеспе хабарларды басқа құрылғыларға жеткізуге рұқсат беріледі."</string>
<string name="permlab_startForegroundServicesFromBackground" msgid="6363004936218638382">"Экрандық режимдегі қызметтерді фоннан іске қосу"</string>
<string name="permdesc_startForegroundServicesFromBackground" msgid="4071826571656001537">"Қосымша қолданбаға экрандық режимдегі қызметтерді фоннан іске қосуға рұқсат беріледі."</string>
+ <!-- no translation found for mic_access_on_toast (2666925317663845156) -->
+ <skip />
+ <!-- no translation found for mic_access_off_toast (8111040892954242437) -->
+ <skip />
</resources>
diff --git a/core/res/res/values-km/strings.xml b/core/res/res/values-km/strings.xml
index 4537735..dd1a3ea 100644
--- a/core/res/res/values-km/strings.xml
+++ b/core/res/res/values-km/strings.xml
@@ -2352,4 +2352,8 @@
<string name="permdesc_deliverCompanionMessages" msgid="2170847384281412850">"អនុញ្ញាតឱ្យកម្មវិធីដៃគូបញ្ជូនសារដៃគូទៅកាន់ឧបករណ៍ផ្សេងទៀត។"</string>
<string name="permlab_startForegroundServicesFromBackground" msgid="6363004936218638382">"ចាប់ផ្តើមសេវាកម្មផ្ទៃខាងមុខពីផ្ទៃខាងក្រោយ"</string>
<string name="permdesc_startForegroundServicesFromBackground" msgid="4071826571656001537">"អនុញ្ញាតឱ្យកម្មវិធីដៃគូចាប់ផ្តើមសេវាកម្មផ្ទៃខាងមុខពីផ្ទៃខាងក្រោយ។"</string>
+ <!-- no translation found for mic_access_on_toast (2666925317663845156) -->
+ <skip />
+ <!-- no translation found for mic_access_off_toast (8111040892954242437) -->
+ <skip />
</resources>
diff --git a/core/res/res/values-kn/strings.xml b/core/res/res/values-kn/strings.xml
index 2817b36..be4c5bc 100644
--- a/core/res/res/values-kn/strings.xml
+++ b/core/res/res/values-kn/strings.xml
@@ -2352,4 +2352,8 @@
<string name="permdesc_deliverCompanionMessages" msgid="2170847384281412850">"ಇತರ ಸಾಧನಗಳಿಗೆ ಕಂಪ್ಯಾನಿಯನ್ ಸಂದೇಶಗಳನ್ನು ತಲುಪಿಸಲು ಕಂಪ್ಯಾನಿಯನ್ ಆ್ಯಪ್ಗೆ ಅನುಮತಿಸುತ್ತದೆ."</string>
<string name="permlab_startForegroundServicesFromBackground" msgid="6363004936218638382">"ಮುನ್ನೆಲೆ ಸೇವೆಗಳನ್ನು ಹಿನ್ನೆಲೆಯಿಂದ ಪ್ರಾರಂಭಿಸಿ"</string>
<string name="permdesc_startForegroundServicesFromBackground" msgid="4071826571656001537">"ಮುನ್ನೆಲೆ ಸೇವೆಗಳನ್ನು ಹಿನ್ನೆಲೆಯಿಂದ ಪ್ರಾರಂಭಿಸಲು ಕಂಪ್ಯಾನಿಯನ್ ಆ್ಯಪ್ಗೆ ಅನುಮತಿಸುತ್ತದೆ."</string>
+ <!-- no translation found for mic_access_on_toast (2666925317663845156) -->
+ <skip />
+ <!-- no translation found for mic_access_off_toast (8111040892954242437) -->
+ <skip />
</resources>
diff --git a/core/res/res/values-ko/strings.xml b/core/res/res/values-ko/strings.xml
index 3727fab..5c96487 100644
--- a/core/res/res/values-ko/strings.xml
+++ b/core/res/res/values-ko/strings.xml
@@ -2352,4 +2352,8 @@
<string name="permdesc_deliverCompanionMessages" msgid="2170847384281412850">"호환 앱에서 호환 메시지를 다른 기기로 전달하도록 허용합니다."</string>
<string name="permlab_startForegroundServicesFromBackground" msgid="6363004936218638382">"백그라운드에서 포그라운드 서비스 시작"</string>
<string name="permdesc_startForegroundServicesFromBackground" msgid="4071826571656001537">"호환 앱이 백그라운드에서 포그라운드 서비스를 시작할 수 있게 허용합니다."</string>
+ <!-- no translation found for mic_access_on_toast (2666925317663845156) -->
+ <skip />
+ <!-- no translation found for mic_access_off_toast (8111040892954242437) -->
+ <skip />
</resources>
diff --git a/core/res/res/values-ky/strings.xml b/core/res/res/values-ky/strings.xml
index 06db92d..c9b7c51 100644
--- a/core/res/res/values-ky/strings.xml
+++ b/core/res/res/values-ky/strings.xml
@@ -2352,4 +2352,8 @@
<string name="permdesc_deliverCompanionMessages" msgid="2170847384281412850">"Көмөкчү колдонмого билдирүүлөрдү башка түзмөктөргө жөнөтүүгө уруксат берет."</string>
<string name="permlab_startForegroundServicesFromBackground" msgid="6363004936218638382">"Активдүү кызматтарды фондо иштетүү"</string>
<string name="permdesc_startForegroundServicesFromBackground" msgid="4071826571656001537">"Көмөкчү колдонмого активдүү кызматтарды фондо иштетүүгө уруксат берет."</string>
+ <!-- no translation found for mic_access_on_toast (2666925317663845156) -->
+ <skip />
+ <!-- no translation found for mic_access_off_toast (8111040892954242437) -->
+ <skip />
</resources>
diff --git a/core/res/res/values-lo/strings.xml b/core/res/res/values-lo/strings.xml
index d1650bb..318945b 100644
--- a/core/res/res/values-lo/strings.xml
+++ b/core/res/res/values-lo/strings.xml
@@ -2352,4 +2352,8 @@
<string name="permdesc_deliverCompanionMessages" msgid="2170847384281412850">"ອະນຸຍາດໃຫ້ແອັບຊ່ວຍເຫຼືອສົ່ງຂໍ້ຄວາມຊ່ວຍເຫຼືອໄປຫາອຸປະກອນອື່ນໆ."</string>
<string name="permlab_startForegroundServicesFromBackground" msgid="6363004936218638382">"ເລີ່ມໃຊ້ບໍລິການທີ່ເຮັດວຽກຢູ່ເບື້ອງໜ້າໂດຍໃຫ້ອະນຸຍາດຈາກເບື້ອງຫຼັງ"</string>
<string name="permdesc_startForegroundServicesFromBackground" msgid="4071826571656001537">"ອະນຸຍາດຈາກເບື້ອງຫຼັງໃຫ້ແອັບຊ່ວຍເຫຼືອເລີ່ມໃຊ້ບໍລິການທີ່ເຮັດວຽກຢູ່ເບື້ອງໜ້າ."</string>
+ <!-- no translation found for mic_access_on_toast (2666925317663845156) -->
+ <skip />
+ <!-- no translation found for mic_access_off_toast (8111040892954242437) -->
+ <skip />
</resources>
diff --git a/core/res/res/values-lt/strings.xml b/core/res/res/values-lt/strings.xml
index 47fd09b..688e881 100644
--- a/core/res/res/values-lt/strings.xml
+++ b/core/res/res/values-lt/strings.xml
@@ -2354,4 +2354,8 @@
<string name="permdesc_deliverCompanionMessages" msgid="2170847384281412850">"Leidžiama papildomai programai teikti papildomos programos pranešimus į kitus įrenginius."</string>
<string name="permlab_startForegroundServicesFromBackground" msgid="6363004936218638382">"Priekinio plano paslaugų paleidimas fone"</string>
<string name="permdesc_startForegroundServicesFromBackground" msgid="4071826571656001537">"Leidžiama papildomai programai paleisti priekinio plano paslaugas fone."</string>
+ <!-- no translation found for mic_access_on_toast (2666925317663845156) -->
+ <skip />
+ <!-- no translation found for mic_access_off_toast (8111040892954242437) -->
+ <skip />
</resources>
diff --git a/core/res/res/values-lv/strings.xml b/core/res/res/values-lv/strings.xml
index 39fd849..8b9f27f 100644
--- a/core/res/res/values-lv/strings.xml
+++ b/core/res/res/values-lv/strings.xml
@@ -2353,4 +2353,8 @@
<string name="permdesc_deliverCompanionMessages" msgid="2170847384281412850">"Ļauj palīglietotnei piegādāt palīgziņojumus citām ierīcēm."</string>
<string name="permlab_startForegroundServicesFromBackground" msgid="6363004936218638382">"Sākt priekšplāna pakalpojumus no fona"</string>
<string name="permdesc_startForegroundServicesFromBackground" msgid="4071826571656001537">"Ļauj palīglietotnei sākt priekšplāna pakalpojumus no fona."</string>
+ <!-- no translation found for mic_access_on_toast (2666925317663845156) -->
+ <skip />
+ <!-- no translation found for mic_access_off_toast (8111040892954242437) -->
+ <skip />
</resources>
diff --git a/core/res/res/values-mcc001-mnc01-b+sr+Latn/strings.xml b/core/res/res/values-mcc001-mnc01-b+sr+Latn/strings.xml
index 7c0fc70..b003fe0 100644
--- a/core/res/res/values-mcc001-mnc01-b+sr+Latn/strings.xml
+++ b/core/res/res/values-mcc001-mnc01-b+sr+Latn/strings.xml
@@ -20,5 +20,5 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="mmcc_illegal_me" msgid="6819499009131365312">"Telefon nije dozvoljen MM#6"</string>
+ <string name="mmcc_illegal_me" msgid="6819499009131365312">"Телефон није дозвољен MM#6"</string>
</resources>
diff --git a/core/res/res/values-mcc310-mnc030-b+sr+Latn/strings.xml b/core/res/res/values-mcc310-mnc030-b+sr+Latn/strings.xml
index 81b9be8..b0a8ae5 100644
--- a/core/res/res/values-mcc310-mnc030-b+sr+Latn/strings.xml
+++ b/core/res/res/values-mcc310-mnc030-b+sr+Latn/strings.xml
@@ -20,7 +20,7 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="mmcc_imsi_unknown_in_hlr" msgid="656054059094417927">"SIM kartica nije podešena MM#2"</string>
- <string name="mmcc_illegal_ms" msgid="1782569305985001089">"SIM kartica nije dozvoljena MM#3"</string>
- <string name="mmcc_illegal_me" msgid="8246632898824321280">"Telefon nije dozvoljen MM#6"</string>
+ <string name="mmcc_imsi_unknown_in_hlr" msgid="656054059094417927">"SIM картица није подешена MM#2"</string>
+ <string name="mmcc_illegal_ms" msgid="1782569305985001089">"SIM картица није дозвољена MM#3"</string>
+ <string name="mmcc_illegal_me" msgid="8246632898824321280">"Телефон није дозвољен MM#6"</string>
</resources>
diff --git a/core/res/res/values-mcc310-mnc150-b+sr+Latn/strings.xml b/core/res/res/values-mcc310-mnc150-b+sr+Latn/strings.xml
index d066eb62..c70f4b7 100644
--- a/core/res/res/values-mcc310-mnc150-b+sr+Latn/strings.xml
+++ b/core/res/res/values-mcc310-mnc150-b+sr+Latn/strings.xml
@@ -20,5 +20,5 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="mmcc_illegal_me" msgid="8004509200390992737">"Telefon nije dozvoljen MM#6"</string>
+ <string name="mmcc_illegal_me" msgid="8004509200390992737">"Телефон није дозвољен MM#6"</string>
</resources>
diff --git a/core/res/res/values-mcc310-mnc170-b+sr+Latn/strings.xml b/core/res/res/values-mcc310-mnc170-b+sr+Latn/strings.xml
index ff89bab..011a40d 100644
--- a/core/res/res/values-mcc310-mnc170-b+sr+Latn/strings.xml
+++ b/core/res/res/values-mcc310-mnc170-b+sr+Latn/strings.xml
@@ -20,7 +20,7 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="mmcc_imsi_unknown_in_hlr" msgid="5424518490295341205">"SIM kartica nije podešena MM#2"</string>
- <string name="mmcc_illegal_ms" msgid="3527626511418944853">"SIM kartica nije dozvoljena MM#3"</string>
- <string name="mmcc_illegal_me" msgid="3948912590657398489">"Telefon nije dozvoljen MM#6"</string>
+ <string name="mmcc_imsi_unknown_in_hlr" msgid="5424518490295341205">"SIM картица није подешена MM#2"</string>
+ <string name="mmcc_illegal_ms" msgid="3527626511418944853">"SIM картица није дозвољена MM#3"</string>
+ <string name="mmcc_illegal_me" msgid="3948912590657398489">"Телефон није дозвољен MM#6"</string>
</resources>
diff --git a/core/res/res/values-mcc310-mnc280-b+sr+Latn/strings.xml b/core/res/res/values-mcc310-mnc280-b+sr+Latn/strings.xml
index faf51d3..9b0dcf1 100644
--- a/core/res/res/values-mcc310-mnc280-b+sr+Latn/strings.xml
+++ b/core/res/res/values-mcc310-mnc280-b+sr+Latn/strings.xml
@@ -20,7 +20,7 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="mmcc_imsi_unknown_in_hlr" msgid="1070849538022865416">"SIM kartica nije podešena MM#2"</string>
- <string name="mmcc_illegal_ms" msgid="499832197298480670">"SIM kartica nije dozvoljena MM#3"</string>
- <string name="mmcc_illegal_me" msgid="2346111479504469688">"Telefon nije dozvoljen MM#6"</string>
+ <string name="mmcc_imsi_unknown_in_hlr" msgid="1070849538022865416">"SIM картица није подешена MM#2"</string>
+ <string name="mmcc_illegal_ms" msgid="499832197298480670">"SIM картица није дозвољена MM#3"</string>
+ <string name="mmcc_illegal_me" msgid="2346111479504469688">"Телефон није дозвољен MM#6"</string>
</resources>
diff --git a/core/res/res/values-mcc310-mnc380-b+sr+Latn/strings.xml b/core/res/res/values-mcc310-mnc380-b+sr+Latn/strings.xml
index e348095..256ad83b 100644
--- a/core/res/res/values-mcc310-mnc380-b+sr+Latn/strings.xml
+++ b/core/res/res/values-mcc310-mnc380-b+sr+Latn/strings.xml
@@ -20,6 +20,6 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="mmcc_imsi_unknown_in_hlr" msgid="6178029798083341927">"SIM kartica nije podešena MM#2"</string>
- <string name="mmcc_illegal_ms" msgid="6084322234976891423">"SIM kartica nije dozvoljena MM#3"</string>
+ <string name="mmcc_imsi_unknown_in_hlr" msgid="6178029798083341927">"SIM картица није подешена MM#2"</string>
+ <string name="mmcc_illegal_ms" msgid="6084322234976891423">"SIM картица није дозвољена MM#3"</string>
</resources>
diff --git a/core/res/res/values-mcc310-mnc410-b+sr+Latn/strings.xml b/core/res/res/values-mcc310-mnc410-b+sr+Latn/strings.xml
index 940507b..e8835d2 100644
--- a/core/res/res/values-mcc310-mnc410-b+sr+Latn/strings.xml
+++ b/core/res/res/values-mcc310-mnc410-b+sr+Latn/strings.xml
@@ -20,7 +20,7 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="mmcc_imsi_unknown_in_hlr" msgid="8861901652350883183">"SIM kartica nije podešena MM#2"</string>
- <string name="mmcc_illegal_ms" msgid="2604694337529846283">"SIM kartica nije dozvoljena MM#3"</string>
- <string name="mmcc_illegal_me" msgid="3099618295079374317">"Telefon nije dozvoljen MM#6"</string>
+ <string name="mmcc_imsi_unknown_in_hlr" msgid="8861901652350883183">"SIM картица није подешена MM#2"</string>
+ <string name="mmcc_illegal_ms" msgid="2604694337529846283">"SIM картица није дозвољена MM#3"</string>
+ <string name="mmcc_illegal_me" msgid="3099618295079374317">"Телефон није дозвољен MM#6"</string>
</resources>
diff --git a/core/res/res/values-mcc310-mnc560-b+sr+Latn/strings.xml b/core/res/res/values-mcc310-mnc560-b+sr+Latn/strings.xml
index 2a0e83c..bacde69 100644
--- a/core/res/res/values-mcc310-mnc560-b+sr+Latn/strings.xml
+++ b/core/res/res/values-mcc310-mnc560-b+sr+Latn/strings.xml
@@ -20,7 +20,7 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="mmcc_imsi_unknown_in_hlr" msgid="3526528316378889524">"SIM kartica nije podešena MM#2"</string>
- <string name="mmcc_illegal_ms" msgid="4618730283812066268">"SIM kartica nije dozvoljena MM#3"</string>
- <string name="mmcc_illegal_me" msgid="8522039751358990401">"Telefon nije dozvoljen MM#6"</string>
+ <string name="mmcc_imsi_unknown_in_hlr" msgid="3526528316378889524">"SIM картица није подешена MM#2"</string>
+ <string name="mmcc_illegal_ms" msgid="4618730283812066268">"SIM картица није дозвољена MM#3"</string>
+ <string name="mmcc_illegal_me" msgid="8522039751358990401">"Телефон није дозвољен MM#6"</string>
</resources>
diff --git a/core/res/res/values-mcc311-mnc180-b+sr+Latn/strings.xml b/core/res/res/values-mcc311-mnc180-b+sr+Latn/strings.xml
index 6cbc6ee..baec8cd 100644
--- a/core/res/res/values-mcc311-mnc180-b+sr+Latn/strings.xml
+++ b/core/res/res/values-mcc311-mnc180-b+sr+Latn/strings.xml
@@ -20,7 +20,7 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="mmcc_imsi_unknown_in_hlr" msgid="604133804161351810">"SIM kartica nije podešena MM#2"</string>
- <string name="mmcc_illegal_ms" msgid="4073997279280371621">"SIM kartica nije dozvoljena MM#3"</string>
- <string name="mmcc_illegal_me" msgid="4936539345546223576">"Telefon nije dozvoljen MM#6"</string>
+ <string name="mmcc_imsi_unknown_in_hlr" msgid="604133804161351810">"SIM картица није подешена MM#2"</string>
+ <string name="mmcc_illegal_ms" msgid="4073997279280371621">"SIM картица није дозвољена MM#3"</string>
+ <string name="mmcc_illegal_me" msgid="4936539345546223576">"Телефон није дозвољен MM#6"</string>
</resources>
diff --git a/core/res/res/values-mcc312-mnc670-b+sr+Latn/strings.xml b/core/res/res/values-mcc312-mnc670-b+sr+Latn/strings.xml
index d9b6793..6c738a2 100644
--- a/core/res/res/values-mcc312-mnc670-b+sr+Latn/strings.xml
+++ b/core/res/res/values-mcc312-mnc670-b+sr+Latn/strings.xml
@@ -20,5 +20,5 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="mmcc_illegal_me" msgid="8271599016773350087">"Telefon nije dozvoljen MM#6"</string>
+ <string name="mmcc_illegal_me" msgid="8271599016773350087">"Телефон није дозвољен MM#6"</string>
</resources>
diff --git a/core/res/res/values-mcc334-mnc020-b+sr+Latn/strings.xml b/core/res/res/values-mcc334-mnc020-b+sr+Latn/strings.xml
index b27e485..ddf0ce1 100644
--- a/core/res/res/values-mcc334-mnc020-b+sr+Latn/strings.xml
+++ b/core/res/res/values-mcc334-mnc020-b+sr+Latn/strings.xml
@@ -20,7 +20,7 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="config_pdp_reject_dialog_title" msgid="41208110171880430"></string>
- <string name="config_pdp_reject_user_authentication_failed" msgid="4683454131283459978">"POTVRDA IDENTITETA NIJE USPELA -29-"</string>
- <string name="config_pdp_reject_service_not_subscribed" msgid="9021140729932308119">"NISTE PRETPLAĆENI NA USLUGU -33-"</string>
- <string name="config_pdp_reject_multi_conn_to_same_pdn_not_allowed" msgid="3838388706348367865">"Nije dozvoljeno više PDN veza za određeni APN -55-"</string>
+ <string name="config_pdp_reject_user_authentication_failed" msgid="4683454131283459978">"ПОТВРДА ИДЕНТИТЕТА НИЈЕ УСПЕЛА -29-"</string>
+ <string name="config_pdp_reject_service_not_subscribed" msgid="9021140729932308119">"НИСТЕ ПРЕТПЛАЋЕНИ НА УСЛУГУ -33-"</string>
+ <string name="config_pdp_reject_multi_conn_to_same_pdn_not_allowed" msgid="3838388706348367865">"Није дозвољено више PDN веза за одређени APN -55-"</string>
</resources>
diff --git a/core/res/res/values-mk/strings.xml b/core/res/res/values-mk/strings.xml
index 1f85755..b5ba29c 100644
--- a/core/res/res/values-mk/strings.xml
+++ b/core/res/res/values-mk/strings.xml
@@ -2352,4 +2352,8 @@
<string name="permdesc_deliverCompanionMessages" msgid="2170847384281412850">"Дозволува придружна апликација да доставува придружни пораки до други уреди."</string>
<string name="permlab_startForegroundServicesFromBackground" msgid="6363004936218638382">"Стартување услуги во преден план од заднина"</string>
<string name="permdesc_startForegroundServicesFromBackground" msgid="4071826571656001537">"Дозволува придружна апликација да започне услуги во преден план од заднината."</string>
+ <!-- no translation found for mic_access_on_toast (2666925317663845156) -->
+ <skip />
+ <!-- no translation found for mic_access_off_toast (8111040892954242437) -->
+ <skip />
</resources>
diff --git a/core/res/res/values-ml/strings.xml b/core/res/res/values-ml/strings.xml
index 1ebdcba..2001f92 100644
--- a/core/res/res/values-ml/strings.xml
+++ b/core/res/res/values-ml/strings.xml
@@ -2352,4 +2352,8 @@
<string name="permdesc_deliverCompanionMessages" msgid="2170847384281412850">"മറ്റ് ഉപകരണങ്ങളിലേക്ക് സഹകാരി സന്ദേശങ്ങൾ ഡെലിവർ ചെയ്യാൻ സഹകാരി ആപ്പിനെ അനുവദിക്കുന്നു."</string>
<string name="permlab_startForegroundServicesFromBackground" msgid="6363004936218638382">"പശ്ചാത്തലത്തിൽ നിന്ന് ഫോർഗ്രൗണ്ട് സേവനങ്ങൾ ആരംഭിക്കുക"</string>
<string name="permdesc_startForegroundServicesFromBackground" msgid="4071826571656001537">"പശ്ചാത്തലത്തിൽ നിന്ന് ഫോർഗ്രൗണ്ട് സേവനങ്ങൾ ആരംഭിക്കാൻ സഹകാരി ആപ്പിനെ അനുവദിക്കുന്നു."</string>
+ <!-- no translation found for mic_access_on_toast (2666925317663845156) -->
+ <skip />
+ <!-- no translation found for mic_access_off_toast (8111040892954242437) -->
+ <skip />
</resources>
diff --git a/core/res/res/values-mn/strings.xml b/core/res/res/values-mn/strings.xml
index 4512910..a060109 100644
--- a/core/res/res/values-mn/strings.xml
+++ b/core/res/res/values-mn/strings.xml
@@ -2352,4 +2352,8 @@
<string name="permdesc_deliverCompanionMessages" msgid="2170847384281412850">"Дэмжигч аппад бусад төхөөрөмжид дэмжигчийн мессежийг хүргэхийг зөвшөөрнө."</string>
<string name="permlab_startForegroundServicesFromBackground" msgid="6363004936218638382">"Нүүрэн талын үйлчилгээнүүдийг ардаас эхлүүлэх"</string>
<string name="permdesc_startForegroundServicesFromBackground" msgid="4071826571656001537">"Дэмжигч аппад нүүрэн талын үйлчилгээнүүдийг ардаас эхлүүлэхийг зөвшөөрнө."</string>
+ <!-- no translation found for mic_access_on_toast (2666925317663845156) -->
+ <skip />
+ <!-- no translation found for mic_access_off_toast (8111040892954242437) -->
+ <skip />
</resources>
diff --git a/core/res/res/values-mr/strings.xml b/core/res/res/values-mr/strings.xml
index 784d4e3..25b5b92 100644
--- a/core/res/res/values-mr/strings.xml
+++ b/core/res/res/values-mr/strings.xml
@@ -2352,4 +2352,8 @@
<string name="permdesc_deliverCompanionMessages" msgid="2170847384281412850">"सहयोगी अॅपला इतर डिव्हाइसवर सहयोगी मेसेज डिलिव्हर करण्याची अनुमती देते."</string>
<string name="permlab_startForegroundServicesFromBackground" msgid="6363004936218638382">"बॅकग्राउंडमधून फोरग्राउंड सेवा सुरू करा"</string>
<string name="permdesc_startForegroundServicesFromBackground" msgid="4071826571656001537">"सहयोगी अॅपला बॅकग्राउंडमधून फोरग्राउंड सेवा सुरू करण्याची अनुमती देते."</string>
+ <!-- no translation found for mic_access_on_toast (2666925317663845156) -->
+ <skip />
+ <!-- no translation found for mic_access_off_toast (8111040892954242437) -->
+ <skip />
</resources>
diff --git a/core/res/res/values-ms/strings.xml b/core/res/res/values-ms/strings.xml
index 7ff53e8..e71f8e2 100644
--- a/core/res/res/values-ms/strings.xml
+++ b/core/res/res/values-ms/strings.xml
@@ -2352,4 +2352,8 @@
<string name="permdesc_deliverCompanionMessages" msgid="2170847384281412850">"Benarkan apl rakan menghantar mesej rakan kepada peranti lain."</string>
<string name="permlab_startForegroundServicesFromBackground" msgid="6363004936218638382">"Mulakan perkhidmatan latar depan dari latar"</string>
<string name="permdesc_startForegroundServicesFromBackground" msgid="4071826571656001537">"Benarkan apl rakan memulakan perkhidmatan latar depan dari latar."</string>
+ <!-- no translation found for mic_access_on_toast (2666925317663845156) -->
+ <skip />
+ <!-- no translation found for mic_access_off_toast (8111040892954242437) -->
+ <skip />
</resources>
diff --git a/core/res/res/values-my/strings.xml b/core/res/res/values-my/strings.xml
index aac7e3b..789ac5b 100644
--- a/core/res/res/values-my/strings.xml
+++ b/core/res/res/values-my/strings.xml
@@ -2352,4 +2352,8 @@
<string name="permdesc_deliverCompanionMessages" msgid="2170847384281412850">"အခြားစက်များသို့ တွဲသုံးနိုင်သော မက်ဆေ့ဂျ်များပို့ရန် တွဲဖက် အက်ပ်ကို ခွင့်ပြုသည်။"</string>
<string name="permlab_startForegroundServicesFromBackground" msgid="6363004936218638382">"နောက်ခံမှနေ၍ မျက်နှာစာဝန်ဆောင်မှုများ စတင်ခြင်း"</string>
<string name="permdesc_startForegroundServicesFromBackground" msgid="4071826571656001537">"နောက်ခံမှနေ၍ မျက်နှာစာဝန်ဆောင်မှုများ စတင်ရန် တွဲဖက် အက်ပ်ကို ခွင့်ပြုသည်။"</string>
+ <!-- no translation found for mic_access_on_toast (2666925317663845156) -->
+ <skip />
+ <!-- no translation found for mic_access_off_toast (8111040892954242437) -->
+ <skip />
</resources>
diff --git a/core/res/res/values-nb/strings.xml b/core/res/res/values-nb/strings.xml
index 3f7f9b0..ba8f470 100644
--- a/core/res/res/values-nb/strings.xml
+++ b/core/res/res/values-nb/strings.xml
@@ -2352,4 +2352,8 @@
<string name="permdesc_deliverCompanionMessages" msgid="2170847384281412850">"Lar en følgeapp levere meldinger til andre enheter."</string>
<string name="permlab_startForegroundServicesFromBackground" msgid="6363004936218638382">"Start forgrunnstjenester fra bakgrunnen"</string>
<string name="permdesc_startForegroundServicesFromBackground" msgid="4071826571656001537">"Lar en følgeapp starte forgrunnstjenester fra bakgrunnen."</string>
+ <!-- no translation found for mic_access_on_toast (2666925317663845156) -->
+ <skip />
+ <!-- no translation found for mic_access_off_toast (8111040892954242437) -->
+ <skip />
</resources>
diff --git a/core/res/res/values-ne/strings.xml b/core/res/res/values-ne/strings.xml
index 31250f4..9760944 100644
--- a/core/res/res/values-ne/strings.xml
+++ b/core/res/res/values-ne/strings.xml
@@ -2352,4 +2352,8 @@
<string name="permdesc_deliverCompanionMessages" msgid="2170847384281412850">"यसले सहयोगी एपलाई अन्य डिभाइसमा सहयोगी म्यासेजहरू डेलिभर गर्ने अनुमति दिन्छ।"</string>
<string name="permlab_startForegroundServicesFromBackground" msgid="6363004936218638382">"ब्याकग्राउन्डमा फोरग्राउन्ड सेवाहरू चलाउने अनुमति"</string>
<string name="permdesc_startForegroundServicesFromBackground" msgid="4071826571656001537">"यसले सहयोगी एपलाई ब्याकग्राउन्डमा फोरग्राउन्ड सेवाहरू चलाउने अनुमति दिन्छ।"</string>
+ <!-- no translation found for mic_access_on_toast (2666925317663845156) -->
+ <skip />
+ <!-- no translation found for mic_access_off_toast (8111040892954242437) -->
+ <skip />
</resources>
diff --git a/core/res/res/values-nl/strings.xml b/core/res/res/values-nl/strings.xml
index ed18567..9334303 100644
--- a/core/res/res/values-nl/strings.xml
+++ b/core/res/res/values-nl/strings.xml
@@ -2352,4 +2352,8 @@
<string name="permdesc_deliverCompanionMessages" msgid="2170847384281412850">"Hiermee kan een bijbehorende app berichten naar andere apparaten sturen."</string>
<string name="permlab_startForegroundServicesFromBackground" msgid="6363004936218638382">"Service op de voorgrond vanuit de achtergrond starten"</string>
<string name="permdesc_startForegroundServicesFromBackground" msgid="4071826571656001537">"Hiermee kan een bijbehorende app services op de voorgrond vanuit de achtergrond starten."</string>
+ <!-- no translation found for mic_access_on_toast (2666925317663845156) -->
+ <skip />
+ <!-- no translation found for mic_access_off_toast (8111040892954242437) -->
+ <skip />
</resources>
diff --git a/core/res/res/values-or/strings.xml b/core/res/res/values-or/strings.xml
index 611421f..2dd316c 100644
--- a/core/res/res/values-or/strings.xml
+++ b/core/res/res/values-or/strings.xml
@@ -2352,4 +2352,8 @@
<string name="permdesc_deliverCompanionMessages" msgid="2170847384281412850">"ଅନ୍ୟ ଡିଭାଇସଗୁଡ଼ିକରେ ସହଯୋଗୀ ମେସେଜଗୁଡ଼ିକ ଡେଲିଭର କରିବା ପାଇଁ ଏକ ସହଯୋଗୀ ଆପକୁ ଅନୁମତି ଦିଏ।"</string>
<string name="permlab_startForegroundServicesFromBackground" msgid="6363004936218638382">"ପୃଷ୍ଠପଟରୁ ଫୋରଗ୍ରାଉଣ୍ଡ ସେବାଗୁଡ଼ିକ ଆରମ୍ଭ କରନ୍ତୁ"</string>
<string name="permdesc_startForegroundServicesFromBackground" msgid="4071826571656001537">"ପୃଷ୍ଠପଟରୁ ଫୋରଗ୍ରାଉଣ୍ଡ ସେବାଗୁଡ଼ିକ ଆରମ୍ଭ କରିବାକୁ ଏକ ସହଯୋଗୀ ଆପକୁ ଅନୁମତି ଦିଏ।"</string>
+ <!-- no translation found for mic_access_on_toast (2666925317663845156) -->
+ <skip />
+ <!-- no translation found for mic_access_off_toast (8111040892954242437) -->
+ <skip />
</resources>
diff --git a/core/res/res/values-pa/strings.xml b/core/res/res/values-pa/strings.xml
index f9dac96..82ad6c0 100644
--- a/core/res/res/values-pa/strings.xml
+++ b/core/res/res/values-pa/strings.xml
@@ -2352,4 +2352,8 @@
<string name="permdesc_deliverCompanionMessages" msgid="2170847384281412850">"ਹੋਰ ਡੀਵਾਈਸਾਂ \'ਤੇ ਸੰਬੰਧੀ ਸੁਨੇਹੇ ਡਿਲੀਵਰ ਕਰਨ ਲਈ ਸੰਬੰਧੀ ਐਪ ਨੂੰ ਆਗਿਆ ਮਿਲਦੀ ਹੈ।"</string>
<string name="permlab_startForegroundServicesFromBackground" msgid="6363004936218638382">"ਬੈਕਗ੍ਰਾਊਂਡ ਤੋਂ ਫੋਰਗ੍ਰਾਊਂਡ ਸੇਵਾਵਾਂ ਚਾਲੂ ਕਰੋ"</string>
<string name="permdesc_startForegroundServicesFromBackground" msgid="4071826571656001537">"ਸੰਬੰਧੀ ਐਪ ਨੂੰ ਬੈਕਗ੍ਰਾਊਂਡ ਤੋਂ ਫੋਰਗ੍ਰਾਊਂਡ ਸੇਵਾਵਾਂ ਸ਼ੁਰੂ ਕਰਨ ਦੀ ਆਗਿਆ ਮਿਲਦੀ ਹੈ।"</string>
+ <!-- no translation found for mic_access_on_toast (2666925317663845156) -->
+ <skip />
+ <!-- no translation found for mic_access_off_toast (8111040892954242437) -->
+ <skip />
</resources>
diff --git a/core/res/res/values-pl/strings.xml b/core/res/res/values-pl/strings.xml
index c578905..5b160ed 100644
--- a/core/res/res/values-pl/strings.xml
+++ b/core/res/res/values-pl/strings.xml
@@ -2354,4 +2354,8 @@
<string name="permdesc_deliverCompanionMessages" msgid="2170847384281412850">"Zezwala aplikacji towarzyszącej na wysyłanie wiadomości towarzyszących na inne urządzenia."</string>
<string name="permlab_startForegroundServicesFromBackground" msgid="6363004936218638382">"Uruchamianie usług na pierwszym planie podczas działania w tle"</string>
<string name="permdesc_startForegroundServicesFromBackground" msgid="4071826571656001537">"Zezwala aplikacji towarzyszącej na uruchamianie usług działających na pierwszym planie, podczas gdy sama działa w tle."</string>
+ <!-- no translation found for mic_access_on_toast (2666925317663845156) -->
+ <skip />
+ <!-- no translation found for mic_access_off_toast (8111040892954242437) -->
+ <skip />
</resources>
diff --git a/core/res/res/values-pt-rBR/strings.xml b/core/res/res/values-pt-rBR/strings.xml
index 45d019e..a6a281f 100644
--- a/core/res/res/values-pt-rBR/strings.xml
+++ b/core/res/res/values-pt-rBR/strings.xml
@@ -2353,4 +2353,8 @@
<string name="permdesc_deliverCompanionMessages" msgid="2170847384281412850">"Permite que um app complementar envie mensagens complementares para outros dispositivos."</string>
<string name="permlab_startForegroundServicesFromBackground" msgid="6363004936218638382">"Iniciar serviços em primeiro plano estando em segundo plano"</string>
<string name="permdesc_startForegroundServicesFromBackground" msgid="4071826571656001537">"Permite que um app complementar em segundo plano inicie serviços em primeiro plano."</string>
+ <!-- no translation found for mic_access_on_toast (2666925317663845156) -->
+ <skip />
+ <!-- no translation found for mic_access_off_toast (8111040892954242437) -->
+ <skip />
</resources>
diff --git a/core/res/res/values-pt-rPT/strings.xml b/core/res/res/values-pt-rPT/strings.xml
index 5728525..dda4f96 100644
--- a/core/res/res/values-pt-rPT/strings.xml
+++ b/core/res/res/values-pt-rPT/strings.xml
@@ -2353,4 +2353,8 @@
<string name="permdesc_deliverCompanionMessages" msgid="2170847384281412850">"Permite que uma app associada entregue mensagens associadas noutros dispositivos."</string>
<string name="permlab_startForegroundServicesFromBackground" msgid="6363004936218638382">"Iniciar em segundo plano serviços em primeiro plano"</string>
<string name="permdesc_startForegroundServicesFromBackground" msgid="4071826571656001537">"Permite que uma app associada em segundo plano inicie serviços em primeiro plano."</string>
+ <!-- no translation found for mic_access_on_toast (2666925317663845156) -->
+ <skip />
+ <!-- no translation found for mic_access_off_toast (8111040892954242437) -->
+ <skip />
</resources>
diff --git a/core/res/res/values-pt/strings.xml b/core/res/res/values-pt/strings.xml
index 45d019e..a6a281f 100644
--- a/core/res/res/values-pt/strings.xml
+++ b/core/res/res/values-pt/strings.xml
@@ -2353,4 +2353,8 @@
<string name="permdesc_deliverCompanionMessages" msgid="2170847384281412850">"Permite que um app complementar envie mensagens complementares para outros dispositivos."</string>
<string name="permlab_startForegroundServicesFromBackground" msgid="6363004936218638382">"Iniciar serviços em primeiro plano estando em segundo plano"</string>
<string name="permdesc_startForegroundServicesFromBackground" msgid="4071826571656001537">"Permite que um app complementar em segundo plano inicie serviços em primeiro plano."</string>
+ <!-- no translation found for mic_access_on_toast (2666925317663845156) -->
+ <skip />
+ <!-- no translation found for mic_access_off_toast (8111040892954242437) -->
+ <skip />
</resources>
diff --git a/core/res/res/values-ro/strings.xml b/core/res/res/values-ro/strings.xml
index 5774809..02e4a83 100644
--- a/core/res/res/values-ro/strings.xml
+++ b/core/res/res/values-ro/strings.xml
@@ -2353,4 +2353,8 @@
<string name="permdesc_deliverCompanionMessages" msgid="2170847384281412850">"Permite unei aplicații partenere să trimită mesaje dispozitivelor însoțitoare."</string>
<string name="permlab_startForegroundServicesFromBackground" msgid="6363004936218638382">"Să inițieze servicii în prim-plan din fundal"</string>
<string name="permdesc_startForegroundServicesFromBackground" msgid="4071826571656001537">"Permite unei aplicații partenere să inițieze servicii în prim-plan din fundal."</string>
+ <!-- no translation found for mic_access_on_toast (2666925317663845156) -->
+ <skip />
+ <!-- no translation found for mic_access_off_toast (8111040892954242437) -->
+ <skip />
</resources>
diff --git a/core/res/res/values-ru/strings.xml b/core/res/res/values-ru/strings.xml
index b0ff050..e098107 100644
--- a/core/res/res/values-ru/strings.xml
+++ b/core/res/res/values-ru/strings.xml
@@ -2354,4 +2354,8 @@
<string name="permdesc_deliverCompanionMessages" msgid="2170847384281412850">"Сопутствующее приложение сможет доставлять сообщения на другие устройства."</string>
<string name="permlab_startForegroundServicesFromBackground" msgid="6363004936218638382">"Запуск активных служб из фонового режима"</string>
<string name="permdesc_startForegroundServicesFromBackground" msgid="4071826571656001537">"Сопутствующее приложение сможет запускать активные службы из фонового режима."</string>
+ <!-- no translation found for mic_access_on_toast (2666925317663845156) -->
+ <skip />
+ <!-- no translation found for mic_access_off_toast (8111040892954242437) -->
+ <skip />
</resources>
diff --git a/core/res/res/values-si/strings.xml b/core/res/res/values-si/strings.xml
index 81e6b55..ff02130 100644
--- a/core/res/res/values-si/strings.xml
+++ b/core/res/res/values-si/strings.xml
@@ -2352,4 +2352,8 @@
<string name="permdesc_deliverCompanionMessages" msgid="2170847384281412850">"වෙනත් උපාංග වෙත සහායක පණිවිඩ බෙදා හැරීමට සහායක යෙදුමකට ඉඩ දෙයි."</string>
<string name="permlab_startForegroundServicesFromBackground" msgid="6363004936218638382">"පසුබිමේ සිට පෙරබිම් සේවා ආරම්භ කරන්න"</string>
<string name="permdesc_startForegroundServicesFromBackground" msgid="4071826571656001537">"පසුබිමේ සිට පෙරබිම් සේවා ආරම්භ කිරීමට සහායක යෙදුමකට ඉඩ දෙයි."</string>
+ <!-- no translation found for mic_access_on_toast (2666925317663845156) -->
+ <skip />
+ <!-- no translation found for mic_access_off_toast (8111040892954242437) -->
+ <skip />
</resources>
diff --git a/core/res/res/values-sk/strings.xml b/core/res/res/values-sk/strings.xml
index a40d19a..5772286 100644
--- a/core/res/res/values-sk/strings.xml
+++ b/core/res/res/values-sk/strings.xml
@@ -2354,4 +2354,8 @@
<string name="permdesc_deliverCompanionMessages" msgid="2170847384281412850">"Umožňuje sprievodnej aplikácii doručovať sprievodné správy do iných zariadení."</string>
<string name="permlab_startForegroundServicesFromBackground" msgid="6363004936218638382">"Spúšťať služby na popredí z pozadia"</string>
<string name="permdesc_startForegroundServicesFromBackground" msgid="4071826571656001537">"Umožňuje sprievodnej aplikácii spúšťať služby na popredí z pozadia."</string>
+ <!-- no translation found for mic_access_on_toast (2666925317663845156) -->
+ <skip />
+ <!-- no translation found for mic_access_off_toast (8111040892954242437) -->
+ <skip />
</resources>
diff --git a/core/res/res/values-sl/strings.xml b/core/res/res/values-sl/strings.xml
index d254e3f..9aab2b6 100644
--- a/core/res/res/values-sl/strings.xml
+++ b/core/res/res/values-sl/strings.xml
@@ -2354,4 +2354,8 @@
<string name="permdesc_deliverCompanionMessages" msgid="2170847384281412850">"Spremljevalni aplikaciji dovoljuje dostavljanje sporočil v druge naprave."</string>
<string name="permlab_startForegroundServicesFromBackground" msgid="6363004936218638382">"Zaganjanje storitev v ospredju iz ozadja"</string>
<string name="permdesc_startForegroundServicesFromBackground" msgid="4071826571656001537">"Spremljevalni aplikaciji dovoljuje, da storitve v ospredju zažene iz ozadja."</string>
+ <!-- no translation found for mic_access_on_toast (2666925317663845156) -->
+ <skip />
+ <!-- no translation found for mic_access_off_toast (8111040892954242437) -->
+ <skip />
</resources>
diff --git a/core/res/res/values-sq/strings.xml b/core/res/res/values-sq/strings.xml
index f0fb083..c677576 100644
--- a/core/res/res/values-sq/strings.xml
+++ b/core/res/res/values-sq/strings.xml
@@ -2352,4 +2352,8 @@
<string name="permdesc_deliverCompanionMessages" msgid="2170847384281412850">"Lejon një aplikacion shoqërues të dërgojë mesazhe shoqëruese te pajisjet e tjera."</string>
<string name="permlab_startForegroundServicesFromBackground" msgid="6363004936218638382">"Fillo shërbimet në plan të parë nga sfondi"</string>
<string name="permdesc_startForegroundServicesFromBackground" msgid="4071826571656001537">"Lejon një aplikacion shoqërues të fillojë shërbimet në plan të parë nga sfondi."</string>
+ <!-- no translation found for mic_access_on_toast (2666925317663845156) -->
+ <skip />
+ <!-- no translation found for mic_access_off_toast (8111040892954242437) -->
+ <skip />
</resources>
diff --git a/core/res/res/values-sr/strings.xml b/core/res/res/values-sr/strings.xml
index 71ead55..562ffec 100644
--- a/core/res/res/values-sr/strings.xml
+++ b/core/res/res/values-sr/strings.xml
@@ -2353,4 +2353,8 @@
<string name="permdesc_deliverCompanionMessages" msgid="2170847384281412850">"Дозвољава пратећој апликацији да шаље пратеће поруке на друге уређаје."</string>
<string name="permlab_startForegroundServicesFromBackground" msgid="6363004936218638382">"Покретање услуга у првом плану из позадине"</string>
<string name="permdesc_startForegroundServicesFromBackground" msgid="4071826571656001537">"Дозвољава пратећој апликацији да покрене услуге у првом плану из позадине."</string>
+ <!-- no translation found for mic_access_on_toast (2666925317663845156) -->
+ <skip />
+ <!-- no translation found for mic_access_off_toast (8111040892954242437) -->
+ <skip />
</resources>
diff --git a/core/res/res/values-sv/strings.xml b/core/res/res/values-sv/strings.xml
index a1dc313..f77f8e9 100644
--- a/core/res/res/values-sv/strings.xml
+++ b/core/res/res/values-sv/strings.xml
@@ -2352,4 +2352,8 @@
<string name="permdesc_deliverCompanionMessages" msgid="2170847384281412850">"Tillåter en tillhörande app att leverera meddelanden till andra enheter."</string>
<string name="permlab_startForegroundServicesFromBackground" msgid="6363004936218638382">"Starta förgrundstjänster i bakgrunden"</string>
<string name="permdesc_startForegroundServicesFromBackground" msgid="4071826571656001537">"Tillåter att en tillhörande app startar förgrundstjänster i bakgrunden."</string>
+ <!-- no translation found for mic_access_on_toast (2666925317663845156) -->
+ <skip />
+ <!-- no translation found for mic_access_off_toast (8111040892954242437) -->
+ <skip />
</resources>
diff --git a/core/res/res/values-sw/strings.xml b/core/res/res/values-sw/strings.xml
index 1f3dd85..2b90303 100644
--- a/core/res/res/values-sw/strings.xml
+++ b/core/res/res/values-sw/strings.xml
@@ -2366,4 +2366,8 @@
<skip />
<!-- no translation found for permdesc_startForegroundServicesFromBackground (4071826571656001537) -->
<skip />
+ <!-- no translation found for mic_access_on_toast (2666925317663845156) -->
+ <skip />
+ <!-- no translation found for mic_access_off_toast (8111040892954242437) -->
+ <skip />
</resources>
diff --git a/core/res/res/values-ta/strings.xml b/core/res/res/values-ta/strings.xml
index b71fb19..fb13747 100644
--- a/core/res/res/values-ta/strings.xml
+++ b/core/res/res/values-ta/strings.xml
@@ -2352,4 +2352,8 @@
<string name="permdesc_deliverCompanionMessages" msgid="2170847384281412850">"பிற சாதனங்களுக்குத் துணை மெசேஜ்களை வழங்க துணைத் தயாரிப்பு ஆப்ஸை அனுமதிக்கும்."</string>
<string name="permlab_startForegroundServicesFromBackground" msgid="6363004936218638382">"பின்னணியில் இருந்து முன்புலச் சேவைகளைத் தொடங்குதல்"</string>
<string name="permdesc_startForegroundServicesFromBackground" msgid="4071826571656001537">"பின்னணியிலிருந்து முன்புலச் சேவைகளைத் தொடங்க துணைத் தயாரிப்பு ஆப்ஸை அனுமதிக்கும்."</string>
+ <!-- no translation found for mic_access_on_toast (2666925317663845156) -->
+ <skip />
+ <!-- no translation found for mic_access_off_toast (8111040892954242437) -->
+ <skip />
</resources>
diff --git a/core/res/res/values-te/strings.xml b/core/res/res/values-te/strings.xml
index 19a3964..54718ba 100644
--- a/core/res/res/values-te/strings.xml
+++ b/core/res/res/values-te/strings.xml
@@ -1190,7 +1190,7 @@
<string name="delete" msgid="1514113991712129054">"తొలగించండి"</string>
<string name="copyUrl" msgid="6229645005987260230">"URLని కాపీ చేయి"</string>
<string name="selectTextMode" msgid="3225108910999318778">"వచనాన్ని ఎంచుకోండి"</string>
- <string name="undo" msgid="3175318090002654673">"చర్య రద్దు చేయి"</string>
+ <string name="undo" msgid="3175318090002654673">"చర్య రద్దు చేయండి"</string>
<string name="redo" msgid="7231448494008532233">"చర్యను రిపీట్ చేయి"</string>
<string name="autofill" msgid="511224882647795296">"ఆటోఫిల్"</string>
<string name="textSelectionCABTitle" msgid="5151441579532476940">"వచన ఎంపిక"</string>
@@ -1749,7 +1749,7 @@
<string name="accessibility_edit_shortcut_menu_button_title" msgid="239446795930436325">"యాక్సెసిబిలిటీ బటన్తో ఉపయోగించడానికి ఫీచర్లను ఎంచుకోండి"</string>
<string name="accessibility_edit_shortcut_menu_volume_title" msgid="1077294237378645981">"వాల్యూమ్ కీ షార్ట్కట్తో ఉపయోగించడానికి ఫీచర్లను ఎంచుకోండి"</string>
<string name="accessibility_uncheck_legacy_item_warning" msgid="8047830891064817447">"<xliff:g id="SERVICE_NAME">%s</xliff:g> ఆఫ్ చేయబడింది"</string>
- <string name="edit_accessibility_shortcut_menu_button" msgid="8885752738733772935">"షార్ట్కట్లను ఎడిట్ చేయి"</string>
+ <string name="edit_accessibility_shortcut_menu_button" msgid="8885752738733772935">"షార్ట్కట్లను ఎడిట్ చేయండి"</string>
<string name="done_accessibility_shortcut_menu_button" msgid="3668407723770815708">"పూర్తయింది"</string>
<string name="disable_accessibility_shortcut" msgid="5806091378745232383">"షార్ట్కట్ను ఆఫ్ చేయి"</string>
<string name="leave_accessibility_shortcut_on" msgid="6543362062336990814">"షార్ట్కట్ను ఉపయోగించు"</string>
@@ -2352,4 +2352,8 @@
<string name="permdesc_deliverCompanionMessages" msgid="2170847384281412850">"ఇతర పరికరాలకు సహాయక మెసేజ్లను డెలివరీ చేయడానికి సహాయక యాప్ను అనుమతిస్తుంది."</string>
<string name="permlab_startForegroundServicesFromBackground" msgid="6363004936218638382">"ఫోర్గ్రౌండ్ సర్వీస్లను లను బ్యాక్గ్రౌండ్ నుండి ప్రారంభించండి"</string>
<string name="permdesc_startForegroundServicesFromBackground" msgid="4071826571656001537">"బ్యాక్గ్రౌండ్ నుండి ఫోర్గ్రౌండ్ సర్వీస్లను ప్రారంభించడానికి సహాయక యాప్ను అనుమతిస్తుంది."</string>
+ <!-- no translation found for mic_access_on_toast (2666925317663845156) -->
+ <skip />
+ <!-- no translation found for mic_access_off_toast (8111040892954242437) -->
+ <skip />
</resources>
diff --git a/core/res/res/values-th/strings.xml b/core/res/res/values-th/strings.xml
index 80743b1..ff59ce1 100644
--- a/core/res/res/values-th/strings.xml
+++ b/core/res/res/values-th/strings.xml
@@ -2352,4 +2352,8 @@
<string name="permdesc_deliverCompanionMessages" msgid="2170847384281412850">"อนุญาตให้แอปที่ใช้ร่วมกันส่งข้อความที่ใช้ร่วมกันไปยังอุปกรณ์อื่นๆ"</string>
<string name="permlab_startForegroundServicesFromBackground" msgid="6363004936218638382">"เริ่มการทำงานของบริการที่ทำงานอยู่เบื้องหน้าโดยให้อนุญาตจากเบื้องหลัง"</string>
<string name="permdesc_startForegroundServicesFromBackground" msgid="4071826571656001537">"อนุญาตจากเบื้องหลังให้แอปที่ใช้ร่วมกันเริ่มการทำงานของบริการที่ทำงานอยู่เบื้องหน้า"</string>
+ <!-- no translation found for mic_access_on_toast (2666925317663845156) -->
+ <skip />
+ <!-- no translation found for mic_access_off_toast (8111040892954242437) -->
+ <skip />
</resources>
diff --git a/core/res/res/values-tl/strings.xml b/core/res/res/values-tl/strings.xml
index dfe2357..257fdd0 100644
--- a/core/res/res/values-tl/strings.xml
+++ b/core/res/res/values-tl/strings.xml
@@ -2352,4 +2352,8 @@
<string name="permdesc_deliverCompanionMessages" msgid="2170847384281412850">"Nagbibigay-daan sa kasamang app na maghatid ng mga mensahe ng kasamang app sa iba pang device."</string>
<string name="permlab_startForegroundServicesFromBackground" msgid="6363004936218638382">"Magsimula ng mga serbisyo sa foreground mula sa background"</string>
<string name="permdesc_startForegroundServicesFromBackground" msgid="4071826571656001537">"Nagbibigay-daan sa kasamang app na magsimula ng mga serbisyo sa foreground mula sa background."</string>
+ <!-- no translation found for mic_access_on_toast (2666925317663845156) -->
+ <skip />
+ <!-- no translation found for mic_access_off_toast (8111040892954242437) -->
+ <skip />
</resources>
diff --git a/core/res/res/values-tr/strings.xml b/core/res/res/values-tr/strings.xml
index b53fb60..22bb998 100644
--- a/core/res/res/values-tr/strings.xml
+++ b/core/res/res/values-tr/strings.xml
@@ -2352,4 +2352,8 @@
<string name="permdesc_deliverCompanionMessages" msgid="2170847384281412850">"Tamamlayıcı uygulamanın diğer cihazlara tamamlayıcı mesajlar iletmesine izin verir."</string>
<string name="permlab_startForegroundServicesFromBackground" msgid="6363004936218638382">"Arka plandan ön plan hizmetleri başlatma"</string>
<string name="permdesc_startForegroundServicesFromBackground" msgid="4071826571656001537">"Tamamlayıcı uygulamanın arka plandan ön plan hizmetlerini başlatmasına izin verir."</string>
+ <!-- no translation found for mic_access_on_toast (2666925317663845156) -->
+ <skip />
+ <!-- no translation found for mic_access_off_toast (8111040892954242437) -->
+ <skip />
</resources>
diff --git a/core/res/res/values-uk/strings.xml b/core/res/res/values-uk/strings.xml
index ea8efd1..ed035da 100644
--- a/core/res/res/values-uk/strings.xml
+++ b/core/res/res/values-uk/strings.xml
@@ -2354,4 +2354,8 @@
<string name="permdesc_deliverCompanionMessages" msgid="2170847384281412850">"Дозволяє супутньому додатку відображати власні повідомлення на інших пристроях."</string>
<string name="permlab_startForegroundServicesFromBackground" msgid="6363004936218638382">"Запускати активні сервіси у фоновому режимі"</string>
<string name="permdesc_startForegroundServicesFromBackground" msgid="4071826571656001537">"Дозволяє супутньому додатку запускати активні сервіси у фоновому режимі."</string>
+ <!-- no translation found for mic_access_on_toast (2666925317663845156) -->
+ <skip />
+ <!-- no translation found for mic_access_off_toast (8111040892954242437) -->
+ <skip />
</resources>
diff --git a/core/res/res/values-ur/strings.xml b/core/res/res/values-ur/strings.xml
index 11f5f68..936a547 100644
--- a/core/res/res/values-ur/strings.xml
+++ b/core/res/res/values-ur/strings.xml
@@ -2352,4 +2352,8 @@
<string name="permdesc_deliverCompanionMessages" msgid="2170847384281412850">"ساتھی ایپ کو دوسرے آلات پر ساتھی پیغامات ڈیلیور کرنے کی اجازت دیتی ہے۔"</string>
<string name="permlab_startForegroundServicesFromBackground" msgid="6363004936218638382">"پس منظر سے پیش منظر کی سروسز شروع کریں"</string>
<string name="permdesc_startForegroundServicesFromBackground" msgid="4071826571656001537">"ساتھی ایپ کو پس منظر سے پیش منظر کی سروسز شروع کرنے کی اجازت دیتی ہے۔"</string>
+ <!-- no translation found for mic_access_on_toast (2666925317663845156) -->
+ <skip />
+ <!-- no translation found for mic_access_off_toast (8111040892954242437) -->
+ <skip />
</resources>
diff --git a/core/res/res/values-uz/strings.xml b/core/res/res/values-uz/strings.xml
index 83394bb..71ab3e4 100644
--- a/core/res/res/values-uz/strings.xml
+++ b/core/res/res/values-uz/strings.xml
@@ -2352,4 +2352,8 @@
<string name="permdesc_deliverCompanionMessages" msgid="2170847384281412850">"Hamroh ilovaga boshqa qurilmalarga hamroh xabarlarni yetkazib berishga ruxsat beradi."</string>
<string name="permlab_startForegroundServicesFromBackground" msgid="6363004936218638382">"Faol xizmatlarni fonda ishga tushirish"</string>
<string name="permdesc_startForegroundServicesFromBackground" msgid="4071826571656001537">"Hamroh ilovaga faol xizmatlarni fonda ishga tushirishga ruxsat beradi."</string>
+ <!-- no translation found for mic_access_on_toast (2666925317663845156) -->
+ <skip />
+ <!-- no translation found for mic_access_off_toast (8111040892954242437) -->
+ <skip />
</resources>
diff --git a/core/res/res/values-vi/strings.xml b/core/res/res/values-vi/strings.xml
index 6a1438f..3ead629 100644
--- a/core/res/res/values-vi/strings.xml
+++ b/core/res/res/values-vi/strings.xml
@@ -2352,4 +2352,8 @@
<string name="permdesc_deliverCompanionMessages" msgid="2170847384281412850">"Cho phép một ứng dụng đồng hành gửi thông báo đồng hành đến các thiết bị khác."</string>
<string name="permlab_startForegroundServicesFromBackground" msgid="6363004936218638382">"Bắt đầu các dịch vụ trên nền trước từ nền"</string>
<string name="permdesc_startForegroundServicesFromBackground" msgid="4071826571656001537">"Cho phép một ứng dụng đồng hành bắt đầu các dịch vụ trên nền trước từ nền."</string>
+ <!-- no translation found for mic_access_on_toast (2666925317663845156) -->
+ <skip />
+ <!-- no translation found for mic_access_off_toast (8111040892954242437) -->
+ <skip />
</resources>
diff --git a/core/res/res/values-zh-rCN/strings.xml b/core/res/res/values-zh-rCN/strings.xml
index ba49875..551f5e6 100644
--- a/core/res/res/values-zh-rCN/strings.xml
+++ b/core/res/res/values-zh-rCN/strings.xml
@@ -2352,4 +2352,8 @@
<string name="permdesc_deliverCompanionMessages" msgid="2170847384281412850">"允许配套应用向其他设备发送配套消息。"</string>
<string name="permlab_startForegroundServicesFromBackground" msgid="6363004936218638382">"从后台启动前台服务"</string>
<string name="permdesc_startForegroundServicesFromBackground" msgid="4071826571656001537">"允许配套应用从后台启动前台服务。"</string>
+ <!-- no translation found for mic_access_on_toast (2666925317663845156) -->
+ <skip />
+ <!-- no translation found for mic_access_off_toast (8111040892954242437) -->
+ <skip />
</resources>
diff --git a/core/res/res/values-zh-rHK/strings.xml b/core/res/res/values-zh-rHK/strings.xml
index 1a89ea8..2b3480d 100644
--- a/core/res/res/values-zh-rHK/strings.xml
+++ b/core/res/res/values-zh-rHK/strings.xml
@@ -2352,4 +2352,8 @@
<string name="permdesc_deliverCompanionMessages" msgid="2170847384281412850">"允許隨附應用程式傳送隨附訊息至其他裝置。"</string>
<string name="permlab_startForegroundServicesFromBackground" msgid="6363004936218638382">"從背景啟動前景服務"</string>
<string name="permdesc_startForegroundServicesFromBackground" msgid="4071826571656001537">"允許隨附應用程式從背景啟動前景服務。"</string>
+ <!-- no translation found for mic_access_on_toast (2666925317663845156) -->
+ <skip />
+ <!-- no translation found for mic_access_off_toast (8111040892954242437) -->
+ <skip />
</resources>
diff --git a/core/res/res/values-zh-rTW/strings.xml b/core/res/res/values-zh-rTW/strings.xml
index 1dfcd67b..e8e1382 100644
--- a/core/res/res/values-zh-rTW/strings.xml
+++ b/core/res/res/values-zh-rTW/strings.xml
@@ -2352,4 +2352,8 @@
<string name="permdesc_deliverCompanionMessages" msgid="2170847384281412850">"允許隨附應用程式將自身產生的訊息傳送給其他裝置。"</string>
<string name="permlab_startForegroundServicesFromBackground" msgid="6363004936218638382">"從背景啟動前景服務"</string>
<string name="permdesc_startForegroundServicesFromBackground" msgid="4071826571656001537">"允許隨附應用程式從背景啟動前景服務。"</string>
+ <!-- no translation found for mic_access_on_toast (2666925317663845156) -->
+ <skip />
+ <!-- no translation found for mic_access_off_toast (8111040892954242437) -->
+ <skip />
</resources>
diff --git a/core/res/res/values-zu/strings.xml b/core/res/res/values-zu/strings.xml
index e257c3a..d8306be 100644
--- a/core/res/res/values-zu/strings.xml
+++ b/core/res/res/values-zu/strings.xml
@@ -2352,4 +2352,8 @@
<string name="permdesc_deliverCompanionMessages" msgid="2170847384281412850">"Ivumela i-app ehambisanayo ukuletha imilayezo ehambisanayo kwamanye amadivayisi."</string>
<string name="permlab_startForegroundServicesFromBackground" msgid="6363004936218638382">"Qala amasevisi angaphambili kusukela ngemuva"</string>
<string name="permdesc_startForegroundServicesFromBackground" msgid="4071826571656001537">"Ivumela i-app ehambisanayo ukuthi iqale amasevisi angaphambili kusukela ngemuva."</string>
+ <!-- no translation found for mic_access_on_toast (2666925317663845156) -->
+ <skip />
+ <!-- no translation found for mic_access_off_toast (8111040892954242437) -->
+ <skip />
</resources>
diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml
index c8b0601..1072f57 100644
--- a/core/res/res/values/attrs.xml
+++ b/core/res/res/values/attrs.xml
@@ -3812,6 +3812,17 @@
<!-- The BCP-47 Language Tag of the subtype. This replaces
{@link android.R.styleable#InputMethod_Subtype_imeSubtypeLocale}. -->
<attr name="languageTag" format="string" />
+ <!-- The BCP-47 Language Tag of the preferred physical keyboard of the subtype. If it's not
+ specified, {@link android.R.styleable#InputMethod_Subtype_languageTag} will be used.
+ See also
+ {@link android.view.inputmethod.InputMethodSubtype#getPhysicalKeyboardHintLanguageTag}.
+ -->
+ <attr name="physicalKeyboardHintLanguageTag" format="string" />
+ <!-- The layout type of the preferred physical keyboard of the subtype.
+ It matches the layout type string in the keyboard layout definition. See also
+ {@link android.view.inputmethod.InputMethodSubtype#getPhysicalKeyboardHintLayoutType}.
+ -->
+ <attr name="physicalKeyboardHintLayoutType" format="string" />
</declare-styleable>
<!-- Use <code>spell-checker</code> as the root tag of the XML resource that
diff --git a/core/res/res/values/public-staging.xml b/core/res/res/values/public-staging.xml
index 1bc1ca6..d7354ea 100644
--- a/core/res/res/values/public-staging.xml
+++ b/core/res/res/values/public-staging.xml
@@ -119,6 +119,8 @@
<public name="requiredDisplayCategory"/>
<public name="removed_maxConcurrentSessionsCount" />
<public name="visualQueryDetectionService" />
+ <public name="physicalKeyboardHintLanguageTag" />
+ <public name="physicalKeyboardHintLayoutType" />
</staging-public-group>
<staging-public-group type="id" first-id="0x01cd0000">
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index d4aa47c..87298d5 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -1122,6 +1122,11 @@
<!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
<string name="permdesc_systemAlertWindow">This app can appear on top of other apps or other parts of the screen. This may interfere with normal app usage and change the way that other apps appear.</string>
+ <!-- Title of hide overlay windows permission, which allows an app to hide overlay windows from other apps. -->
+ <string name="permlab_hideOverlayWindows">hide other apps overlays</string>
+ <!-- Description of hide overlay windows permission, which allows an app to prevent overlays from being drawn on top of it. [CHAR LIMIT=NONE] -->
+ <string name="permdesc_hideOverlayWindows">This app can request that the system hides overlays originating from apps from being shown on top of it.</string>
+
<!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
<string name="permlab_runInBackground">run in the background</string>
<!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
diff --git a/core/tests/coretests/assets/fonts/a3em.ttf b/core/tests/coretests/assets/fonts/a3em.ttf
index a601ce2..4c1ad1b 100644
--- a/core/tests/coretests/assets/fonts/a3em.ttf
+++ b/core/tests/coretests/assets/fonts/a3em.ttf
Binary files differ
diff --git a/core/tests/coretests/assets/fonts/a3em.ttx b/core/tests/coretests/assets/fonts/a3em.ttx
index d3b9e16..d7da0da 100644
--- a/core/tests/coretests/assets/fonts/a3em.ttx
+++ b/core/tests/coretests/assets/fonts/a3em.ttx
@@ -156,7 +156,7 @@
Sample Font
</namerecord>
<namerecord nameID="6" platformID="3" platEncID="1" langID="0x409">
- SampleFont-Regular
+ a3em
</namerecord>
<namerecord nameID="13" platformID="3" platEncID="1" langID="0x409">
Licensed under the Apache License, Version 2.0 (the "License");
diff --git a/core/tests/coretests/assets/fonts/b3em.ttf b/core/tests/coretests/assets/fonts/b3em.ttf
index 63948a2..b18d4bb 100644
--- a/core/tests/coretests/assets/fonts/b3em.ttf
+++ b/core/tests/coretests/assets/fonts/b3em.ttf
Binary files differ
diff --git a/core/tests/coretests/assets/fonts/b3em.ttx b/core/tests/coretests/assets/fonts/b3em.ttx
index b5a77ef..217393a 100644
--- a/core/tests/coretests/assets/fonts/b3em.ttx
+++ b/core/tests/coretests/assets/fonts/b3em.ttx
@@ -156,7 +156,7 @@
Sample Font
</namerecord>
<namerecord nameID="6" platformID="3" platEncID="1" langID="0x409">
- SampleFont-Regular
+ b3em
</namerecord>
<namerecord nameID="13" platformID="3" platEncID="1" langID="0x409">
Licensed under the Apache License, Version 2.0 (the "License");
diff --git a/core/tests/coretests/assets/fonts/c3em.ttf b/core/tests/coretests/assets/fonts/c3em.ttf
index badc3e2..83a5db2 100644
--- a/core/tests/coretests/assets/fonts/c3em.ttf
+++ b/core/tests/coretests/assets/fonts/c3em.ttf
Binary files differ
diff --git a/core/tests/coretests/assets/fonts/c3em.ttx b/core/tests/coretests/assets/fonts/c3em.ttx
index f5ed8e5..7535bea 100644
--- a/core/tests/coretests/assets/fonts/c3em.ttx
+++ b/core/tests/coretests/assets/fonts/c3em.ttx
@@ -156,7 +156,7 @@
Sample Font
</namerecord>
<namerecord nameID="6" platformID="3" platEncID="1" langID="0x409">
- SampleFont-Regular
+ c3em
</namerecord>
<namerecord nameID="13" platformID="3" platEncID="1" langID="0x409">
Licensed under the Apache License, Version 2.0 (the "License");
diff --git a/core/tests/coretests/assets/fonts/fallback.ttf b/core/tests/coretests/assets/fonts/fallback.ttf
new file mode 100644
index 0000000..1ba8639
--- /dev/null
+++ b/core/tests/coretests/assets/fonts/fallback.ttf
Binary files differ
diff --git a/core/tests/coretests/assets/fonts/fallback.ttx b/core/tests/coretests/assets/fonts/fallback.ttx
new file mode 100644
index 0000000..8e4b3e6
--- /dev/null
+++ b/core/tests/coretests/assets/fonts/fallback.ttx
@@ -0,0 +1,205 @@
+<?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.
+-->
+<ttFont sfntVersion="\x00\x01\x00\x00" ttLibVersion="3.0">
+
+ <GlyphOrder>
+ <GlyphID id="0" name=".notdef"/>
+ <GlyphID id="1" name="1em"/>
+ </GlyphOrder>
+
+ <head>
+ <tableVersion value="1.0"/>
+ <fontRevision value="1.0"/>
+ <checkSumAdjustment value="0x640cdb2f"/>
+ <magicNumber value="0x5f0f3cf5"/>
+ <flags value="00000000 00000011"/>
+ <unitsPerEm value="1000"/>
+ <created value="Fri Mar 17 07:26:00 2017"/>
+ <macStyle value="00000000 00000000"/>
+ <lowestRecPPEM value="7"/>
+ <fontDirectionHint value="2"/>
+ <glyphDataFormat value="0"/>
+ </head>
+
+ <hhea>
+ <tableVersion value="1.0"/>
+ <ascent value="1000"/>
+ <descent value="-200"/>
+ <lineGap value="0"/>
+ <caretSlopeRise value="1"/>
+ <caretSlopeRun value="0"/>
+ <caretOffset value="0"/>
+ <reserved0 value="0"/>
+ <reserved1 value="0"/>
+ <reserved2 value="0"/>
+ <reserved3 value="0"/>
+ <metricDataFormat value="0"/>
+ </hhea>
+
+ <maxp>
+ <tableVersion value="0x10000"/>
+ <maxZones value="0"/>
+ <maxTwilightPoints value="0"/>
+ <maxStorage value="0"/>
+ <maxFunctionDefs value="0"/>
+ <maxInstructionDefs value="0"/>
+ <maxStackElements value="0"/>
+ <maxSizeOfInstructions value="0"/>
+ <maxComponentElements value="0"/>
+ </maxp>
+
+ <OS_2>
+ <!-- The fields 'usFirstCharIndex' and 'usLastCharIndex'
+ will be recalculated by the compiler -->
+ <version value="3"/>
+ <xAvgCharWidth value="594"/>
+ <usWeightClass value="400"/>
+ <usWidthClass value="5"/>
+ <fsType value="00000000 00001000"/>
+ <ySubscriptXSize value="650"/>
+ <ySubscriptYSize value="600"/>
+ <ySubscriptXOffset value="0"/>
+ <ySubscriptYOffset value="75"/>
+ <ySuperscriptXSize value="650"/>
+ <ySuperscriptYSize value="600"/>
+ <ySuperscriptXOffset value="0"/>
+ <ySuperscriptYOffset value="350"/>
+ <yStrikeoutSize value="50"/>
+ <yStrikeoutPosition value="300"/>
+ <sFamilyClass value="0"/>
+ <panose>
+ <bFamilyType value="0"/>
+ <bSerifStyle value="0"/>
+ <bWeight value="5"/>
+ <bProportion value="0"/>
+ <bContrast value="0"/>
+ <bStrokeVariation value="0"/>
+ <bArmStyle value="0"/>
+ <bLetterForm value="0"/>
+ <bMidline value="0"/>
+ <bXHeight value="0"/>
+ </panose>
+ <ulUnicodeRange1 value="00000000 00000000 00000000 00000001"/>
+ <ulUnicodeRange2 value="00000000 00000000 00000000 00000000"/>
+ <ulUnicodeRange3 value="00000000 00000000 00000000 00000000"/>
+ <ulUnicodeRange4 value="00000000 00000000 00000000 00000000"/>
+ <achVendID value="UKWN"/>
+ <fsSelection value="00000000 01000000"/>
+ <usFirstCharIndex value="32"/>
+ <usLastCharIndex value="122"/>
+ <sTypoAscender value="800"/>
+ <sTypoDescender value="-200"/>
+ <sTypoLineGap value="200"/>
+ <usWinAscent value="1000"/>
+ <usWinDescent value="200"/>
+ <ulCodePageRange1 value="00000000 00000000 00000000 00000001"/>
+ <ulCodePageRange2 value="00000000 00000000 00000000 00000000"/>
+ <sxHeight value="500"/>
+ <sCapHeight value="700"/>
+ <usDefaultChar value="0"/>
+ <usBreakChar value="32"/>
+ <usMaxContext value="0"/>
+ </OS_2>
+
+ <hmtx>
+ <mtx name=".notdef" width="500" lsb="93"/>
+ <mtx name="1em" width="1000" lsb="93"/>
+ </hmtx>
+
+ <cmap>
+ <tableVersion version="0"/>
+ <cmap_format_4 platformID="3" platEncID="10" language="0">
+ <map code="0x0061" name="1em" /> <!-- "a" -->
+ <map code="0x0062" name="1em" /> <!-- "b" -->
+ <map code="0x0063" name="1em" /> <!-- "c" -->
+ <map code="0x0064" name="1em" /> <!-- "d" -->
+ <map code="0x0065" name="1em" /> <!-- "e" -->
+ <map code="0x0066" name="1em" /> <!-- "f" -->
+ <map code="0x0067" name="1em" /> <!-- "g" -->
+ <map code="0x0068" name="1em" /> <!-- "h" -->
+ <map code="0x0069" name="1em" /> <!-- "i" -->
+ <map code="0x006a" name="1em" /> <!-- "j" -->
+ <map code="0x006b" name="1em" /> <!-- "k" -->
+ <map code="0x006c" name="1em" /> <!-- "l" -->
+ <map code="0x006d" name="1em" /> <!-- "m" -->
+ <map code="0x006e" name="1em" /> <!-- "n" -->
+ <map code="0x006f" name="1em" /> <!-- "o" -->
+ <map code="0x0070" name="1em" /> <!-- "p" -->
+ <map code="0x0071" name="1em" /> <!-- "q" -->
+ <map code="0x0072" name="1em" /> <!-- "r" -->
+ <map code="0x0073" name="1em" /> <!-- "s" -->
+ <map code="0x0074" name="1em" /> <!-- "t" -->
+ <map code="0x0075" name="1em" /> <!-- "u" -->
+ <map code="0x0076" name="1em" /> <!-- "v" -->
+ <map code="0x0077" name="1em" /> <!-- "w" -->
+ <map code="0x0078" name="1em" /> <!-- "x" -->
+ <map code="0x0079" name="1em" /> <!-- "y" -->
+ <map code="0x007a" name="1em" /> <!-- "z" -->
+ </cmap_format_4>
+ </cmap>
+
+ <loca>
+ <!-- The 'loca' table will be calculated by the compiler -->
+ </loca>
+
+ <glyf>
+ <TTGlyph name=".notdef" xMin="0" yMin="0" xMax="0" yMax="0" />
+ <TTGlyph name="1em" xMin="0" yMin="0" xMax="0" yMax="0" />
+ </glyf>
+
+ <name>
+ <namerecord nameID="0" platformID="3" platEncID="1" langID="0x409">
+ Copyright (C) 2017 The Android Open Source Project
+ </namerecord>
+ <namerecord nameID="1" platformID="3" platEncID="1" langID="0x409">
+ Sample Font
+ </namerecord>
+ <namerecord nameID="2" platformID="3" platEncID="1" langID="0x409">
+ Regular
+ </namerecord>
+ <namerecord nameID="4" platformID="3" platEncID="1" langID="0x409">
+ Sample Font
+ </namerecord>
+ <namerecord nameID="6" platformID="3" platEncID="1" langID="0x409">
+ fallback
+ </namerecord>
+ <namerecord nameID="13" platformID="3" platEncID="1" langID="0x409">
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ </namerecord>
+ <namerecord nameID="14" platformID="3" platEncID="1" langID="0x409">
+ http://www.apache.org/licenses/LICENSE-2.0
+ </namerecord>
+ </name>
+
+ <post>
+ <formatType value="3.0"/>
+ <italicAngle value="0.0"/>
+ <underlinePosition value="-75"/>
+ <underlineThickness value="50"/>
+ <isFixedPitch value="0"/>
+ <minMemType42 value="0"/>
+ <maxMemType42 value="0"/>
+ <minMemType1 value="0"/>
+ <maxMemType1 value="0"/>
+ </post>
+
+</ttFont>
diff --git a/core/tests/coretests/assets/fonts/fallback_capital.ttf b/core/tests/coretests/assets/fonts/fallback_capital.ttf
new file mode 100644
index 0000000..073761f
--- /dev/null
+++ b/core/tests/coretests/assets/fonts/fallback_capital.ttf
Binary files differ
diff --git a/core/tests/coretests/assets/fonts/fallback_capital.ttx b/core/tests/coretests/assets/fonts/fallback_capital.ttx
new file mode 100644
index 0000000..710c95e
--- /dev/null
+++ b/core/tests/coretests/assets/fonts/fallback_capital.ttx
@@ -0,0 +1,205 @@
+<?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.
+-->
+<ttFont sfntVersion="\x00\x01\x00\x00" ttLibVersion="3.0">
+
+ <GlyphOrder>
+ <GlyphID id="0" name=".notdef"/>
+ <GlyphID id="1" name="1em"/>
+ </GlyphOrder>
+
+ <head>
+ <tableVersion value="1.0"/>
+ <fontRevision value="1.0"/>
+ <checkSumAdjustment value="0x640cdb2f"/>
+ <magicNumber value="0x5f0f3cf5"/>
+ <flags value="00000000 00000011"/>
+ <unitsPerEm value="1000"/>
+ <created value="Fri Mar 17 07:26:00 2017"/>
+ <macStyle value="00000000 00000000"/>
+ <lowestRecPPEM value="7"/>
+ <fontDirectionHint value="2"/>
+ <glyphDataFormat value="0"/>
+ </head>
+
+ <hhea>
+ <tableVersion value="1.0"/>
+ <ascent value="1000"/>
+ <descent value="-200"/>
+ <lineGap value="0"/>
+ <caretSlopeRise value="1"/>
+ <caretSlopeRun value="0"/>
+ <caretOffset value="0"/>
+ <reserved0 value="0"/>
+ <reserved1 value="0"/>
+ <reserved2 value="0"/>
+ <reserved3 value="0"/>
+ <metricDataFormat value="0"/>
+ </hhea>
+
+ <maxp>
+ <tableVersion value="0x10000"/>
+ <maxZones value="0"/>
+ <maxTwilightPoints value="0"/>
+ <maxStorage value="0"/>
+ <maxFunctionDefs value="0"/>
+ <maxInstructionDefs value="0"/>
+ <maxStackElements value="0"/>
+ <maxSizeOfInstructions value="0"/>
+ <maxComponentElements value="0"/>
+ </maxp>
+
+ <OS_2>
+ <!-- The fields 'usFirstCharIndex' and 'usLastCharIndex'
+ will be recalculated by the compiler -->
+ <version value="3"/>
+ <xAvgCharWidth value="594"/>
+ <usWeightClass value="400"/>
+ <usWidthClass value="5"/>
+ <fsType value="00000000 00001000"/>
+ <ySubscriptXSize value="650"/>
+ <ySubscriptYSize value="600"/>
+ <ySubscriptXOffset value="0"/>
+ <ySubscriptYOffset value="75"/>
+ <ySuperscriptXSize value="650"/>
+ <ySuperscriptYSize value="600"/>
+ <ySuperscriptXOffset value="0"/>
+ <ySuperscriptYOffset value="350"/>
+ <yStrikeoutSize value="50"/>
+ <yStrikeoutPosition value="300"/>
+ <sFamilyClass value="0"/>
+ <panose>
+ <bFamilyType value="0"/>
+ <bSerifStyle value="0"/>
+ <bWeight value="5"/>
+ <bProportion value="0"/>
+ <bContrast value="0"/>
+ <bStrokeVariation value="0"/>
+ <bArmStyle value="0"/>
+ <bLetterForm value="0"/>
+ <bMidline value="0"/>
+ <bXHeight value="0"/>
+ </panose>
+ <ulUnicodeRange1 value="00000000 00000000 00000000 00000001"/>
+ <ulUnicodeRange2 value="00000000 00000000 00000000 00000000"/>
+ <ulUnicodeRange3 value="00000000 00000000 00000000 00000000"/>
+ <ulUnicodeRange4 value="00000000 00000000 00000000 00000000"/>
+ <achVendID value="UKWN"/>
+ <fsSelection value="00000000 01000000"/>
+ <usFirstCharIndex value="32"/>
+ <usLastCharIndex value="122"/>
+ <sTypoAscender value="800"/>
+ <sTypoDescender value="-200"/>
+ <sTypoLineGap value="200"/>
+ <usWinAscent value="1000"/>
+ <usWinDescent value="200"/>
+ <ulCodePageRange1 value="00000000 00000000 00000000 00000001"/>
+ <ulCodePageRange2 value="00000000 00000000 00000000 00000000"/>
+ <sxHeight value="500"/>
+ <sCapHeight value="700"/>
+ <usDefaultChar value="0"/>
+ <usBreakChar value="32"/>
+ <usMaxContext value="0"/>
+ </OS_2>
+
+ <hmtx>
+ <mtx name=".notdef" width="500" lsb="93"/>
+ <mtx name="1em" width="1000" lsb="93"/>
+ </hmtx>
+
+ <cmap>
+ <tableVersion version="0"/>
+ <cmap_format_4 platformID="3" platEncID="10" language="0">
+ <map code="0x0041" name="1em" /> <!-- "A" -->
+ <map code="0x0042" name="1em" /> <!-- "B" -->
+ <map code="0x0043" name="1em" /> <!-- "C" -->
+ <map code="0x0044" name="1em" /> <!-- "D" -->
+ <map code="0x0045" name="1em" /> <!-- "E" -->
+ <map code="0x0046" name="1em" /> <!-- "F" -->
+ <map code="0x0047" name="1em" /> <!-- "G" -->
+ <map code="0x0048" name="1em" /> <!-- "H" -->
+ <map code="0x0049" name="1em" /> <!-- "I" -->
+ <map code="0x004a" name="1em" /> <!-- "J" -->
+ <map code="0x004b" name="1em" /> <!-- "K" -->
+ <map code="0x004c" name="1em" /> <!-- "L" -->
+ <map code="0x004d" name="1em" /> <!-- "M" -->
+ <map code="0x004e" name="1em" /> <!-- "N" -->
+ <map code="0x004f" name="1em" /> <!-- "O" -->
+ <map code="0x0050" name="1em" /> <!-- "P" -->
+ <map code="0x0051" name="1em" /> <!-- "Q" -->
+ <map code="0x0052" name="1em" /> <!-- "R" -->
+ <map code="0x0053" name="1em" /> <!-- "S" -->
+ <map code="0x0054" name="1em" /> <!-- "T" -->
+ <map code="0x0055" name="1em" /> <!-- "U" -->
+ <map code="0x0056" name="1em" /> <!-- "V" -->
+ <map code="0x0057" name="1em" /> <!-- "W" -->
+ <map code="0x0058" name="1em" /> <!-- "X" -->
+ <map code="0x0059" name="1em" /> <!-- "Y" -->
+ <map code="0x005a" name="1em" /> <!-- "Z" -->
+ </cmap_format_4>
+ </cmap>
+
+ <loca>
+ <!-- The 'loca' table will be calculated by the compiler -->
+ </loca>
+
+ <glyf>
+ <TTGlyph name=".notdef" xMin="0" yMin="0" xMax="0" yMax="0" />
+ <TTGlyph name="1em" xMin="0" yMin="0" xMax="0" yMax="0" />
+ </glyf>
+
+ <name>
+ <namerecord nameID="0" platformID="3" platEncID="1" langID="0x409">
+ Copyright (C) 2017 The Android Open Source Project
+ </namerecord>
+ <namerecord nameID="1" platformID="3" platEncID="1" langID="0x409">
+ Sample Font
+ </namerecord>
+ <namerecord nameID="2" platformID="3" platEncID="1" langID="0x409">
+ Regular
+ </namerecord>
+ <namerecord nameID="4" platformID="3" platEncID="1" langID="0x409">
+ Sample Font
+ </namerecord>
+ <namerecord nameID="6" platformID="3" platEncID="1" langID="0x409">
+ fallback_capital
+ </namerecord>
+ <namerecord nameID="13" platformID="3" platEncID="1" langID="0x409">
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ </namerecord>
+ <namerecord nameID="14" platformID="3" platEncID="1" langID="0x409">
+ http://www.apache.org/licenses/LICENSE-2.0
+ </namerecord>
+ </name>
+
+ <post>
+ <formatType value="3.0"/>
+ <italicAngle value="0.0"/>
+ <underlinePosition value="-75"/>
+ <underlineThickness value="50"/>
+ <isFixedPitch value="0"/>
+ <minMemType42 value="0"/>
+ <maxMemType42 value="0"/>
+ <minMemType1 value="0"/>
+ <maxMemType1 value="0"/>
+ </post>
+
+</ttFont>
diff --git a/core/tests/coretests/src/android/graphics/FontListParserTest.java b/core/tests/coretests/src/android/graphics/FontListParserTest.java
index 479e52a..d46f762 100644
--- a/core/tests/coretests/src/android/graphics/FontListParserTest.java
+++ b/core/tests/coretests/src/android/graphics/FontListParserTest.java
@@ -47,6 +47,7 @@
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
+import java.util.Collections;
import java.util.List;
@SmallTest
@@ -59,14 +60,12 @@
+ "<family name='sans-serif'>"
+ " <font>test.ttf</font>"
+ "</family>";
- FontConfig.FontFamily expected = new FontConfig.FontFamily(
- Arrays.asList(
- new FontConfig.Font(new File("test.ttf"), null, "test",
- new FontStyle(FONT_WEIGHT_NORMAL, FONT_SLANT_UPRIGHT),
- 0, "", null)),
- "sans-serif", LocaleList.getEmptyLocaleList(), VARIANT_DEFAULT);
-
- FontConfig.FontFamily family = readFamily(xml);
+ FontConfig.NamedFamilyList expected = new FontConfig.NamedFamilyList(
+ Collections.singletonList(new FontConfig.FontFamily(
+ Arrays.asList(new FontConfig.Font(new File("test.ttf"), null, "test",
+ new FontStyle(FONT_WEIGHT_NORMAL, FONT_SLANT_UPRIGHT), 0, "", null)),
+ LocaleList.getEmptyLocaleList(), VARIANT_DEFAULT)), "sans-serif");
+ FontConfig.NamedFamilyList family = readNamedFamily(xml);
assertThat(family).isEqualTo(expected);
}
@@ -85,7 +84,7 @@
new FontConfig.Font(new File("test.ttf"), null, "test",
new FontStyle(FONT_WEIGHT_NORMAL, FONT_SLANT_UPRIGHT),
0, "", "serif")),
- null, LocaleList.forLanguageTags("en"), VARIANT_DEFAULT);
+ LocaleList.forLanguageTags("en"), VARIANT_DEFAULT);
FontConfig.FontFamily family = readFamily(xml);
assertThat(family).isEqualTo(expected);
@@ -102,7 +101,7 @@
new FontConfig.Font(new File("test.ttf"), null, "test",
new FontStyle(FONT_WEIGHT_NORMAL, FONT_SLANT_UPRIGHT),
0, "", null)),
- null, LocaleList.forLanguageTags("en"), VARIANT_COMPACT);
+ LocaleList.forLanguageTags("en"), VARIANT_COMPACT);
FontConfig.FontFamily family = readFamily(xml);
assertThat(family).isEqualTo(expected);
@@ -119,7 +118,7 @@
new FontConfig.Font(new File("test.ttf"), null, "test",
new FontStyle(FONT_WEIGHT_NORMAL, FONT_SLANT_UPRIGHT),
0, "", null)),
- null, LocaleList.forLanguageTags("en"), VARIANT_ELEGANT);
+ LocaleList.forLanguageTags("en"), VARIANT_ELEGANT);
FontConfig.FontFamily family = readFamily(xml);
assertThat(family).isEqualTo(expected);
@@ -133,19 +132,16 @@
+ " <font weight='100'>weight.ttf</font>"
+ " <font style='italic'>italic.ttf</font>"
+ "</family>";
- FontConfig.FontFamily expected = new FontConfig.FontFamily(
- Arrays.asList(
- new FontConfig.Font(new File("normal.ttf"), null, "test",
- new FontStyle(FONT_WEIGHT_NORMAL, FONT_SLANT_UPRIGHT),
- 0, "", null),
- new FontConfig.Font(new File("weight.ttf"), null, "test",
- new FontStyle(100, FONT_SLANT_UPRIGHT),
- 0, "", null),
- new FontConfig.Font(new File("italic.ttf"), null, "test",
- new FontStyle(FONT_WEIGHT_NORMAL, FONT_SLANT_ITALIC),
- 0, "", null)),
- "sans-serif", LocaleList.getEmptyLocaleList(), VARIANT_DEFAULT);
- FontConfig.FontFamily family = readFamily(xml);
+ FontConfig.NamedFamilyList expected = new FontConfig.NamedFamilyList(
+ Collections.singletonList(new FontConfig.FontFamily(Arrays.asList(
+ new FontConfig.Font(new File("normal.ttf"), null, "test",
+ new FontStyle(FONT_WEIGHT_NORMAL, FONT_SLANT_UPRIGHT), 0, "", null),
+ new FontConfig.Font(new File("weight.ttf"), null, "test",
+ new FontStyle(100, FONT_SLANT_UPRIGHT), 0, "", null),
+ new FontConfig.Font(new File("italic.ttf"), null, "test",
+ new FontStyle(FONT_WEIGHT_NORMAL, FONT_SLANT_ITALIC), 0, "", null)),
+ LocaleList.getEmptyLocaleList(), VARIANT_DEFAULT)), "sans-serif");
+ FontConfig.NamedFamilyList family = readNamedFamily(xml);
assertThat(family).isEqualTo(expected);
}
@@ -162,16 +158,17 @@
+ " <axis tag='wght' stylevalue='700' />"
+ " </font>"
+ "</family>";
- FontConfig.FontFamily expected = new FontConfig.FontFamily(
- Arrays.asList(
+ FontConfig.NamedFamilyList expected = new FontConfig.NamedFamilyList(
+ Collections.singletonList(new FontConfig.FontFamily(Arrays.asList(
new FontConfig.Font(new File("test-VF.ttf"), null, "test",
new FontStyle(FONT_WEIGHT_NORMAL, FONT_SLANT_UPRIGHT),
0, "'wdth' 100.0,'wght' 200.0", null),
new FontConfig.Font(new File("test-VF.ttf"), null, "test",
new FontStyle(FONT_WEIGHT_NORMAL, FONT_SLANT_UPRIGHT),
0, "'wdth' 400.0,'wght' 700.0", null)),
- "sans-serif", LocaleList.getEmptyLocaleList(), VARIANT_DEFAULT);
- FontConfig.FontFamily family = readFamily(xml);
+ LocaleList.getEmptyLocaleList(), VARIANT_DEFAULT)),
+ "sans-serif");
+ FontConfig.NamedFamilyList family = readNamedFamily(xml);
assertThat(family).isEqualTo(expected);
}
@@ -182,16 +179,17 @@
+ " <font index='0'>test.ttc</font>"
+ " <font index='1'>test.ttc</font>"
+ "</family>";
- FontConfig.FontFamily expected = new FontConfig.FontFamily(
- Arrays.asList(
+ FontConfig.NamedFamilyList expected = new FontConfig.NamedFamilyList(
+ Collections.singletonList(new FontConfig.FontFamily(Arrays.asList(
new FontConfig.Font(new File("test.ttc"), null, "test",
new FontStyle(FONT_WEIGHT_NORMAL, FONT_SLANT_UPRIGHT),
0, "", null),
new FontConfig.Font(new File("test.ttc"), null, "test",
new FontStyle(FONT_WEIGHT_NORMAL, FONT_SLANT_UPRIGHT),
1, "", null)),
- "sans-serif", LocaleList.getEmptyLocaleList(), VARIANT_DEFAULT);
- FontConfig.FontFamily family = readFamily(xml);
+ LocaleList.getEmptyLocaleList(), VARIANT_DEFAULT)),
+ "sans-serif");
+ FontConfig.NamedFamilyList family = readNamedFamily(xml);
assertThat(family).isEqualTo(expected);
}
@@ -202,16 +200,14 @@
+ " <font index='0' postScriptName='foo'>test.ttc</font>"
+ " <font index='1'>test.ttc</font>"
+ "</family>";
- FontConfig.FontFamily expected = new FontConfig.FontFamily(
- Arrays.asList(
- new FontConfig.Font(new File("test.ttc"), null, "foo",
- new FontStyle(FONT_WEIGHT_NORMAL, FONT_SLANT_UPRIGHT),
- 0, "", null),
- new FontConfig.Font(new File("test.ttc"), null, "test",
- new FontStyle(FONT_WEIGHT_NORMAL, FONT_SLANT_UPRIGHT),
- 1, "", null)),
- "sans-serif", LocaleList.getEmptyLocaleList(), VARIANT_DEFAULT);
- FontConfig.FontFamily family = readFamily(xml);
+ FontConfig.NamedFamilyList expected = new FontConfig.NamedFamilyList(
+ Collections.singletonList(new FontConfig.FontFamily(Arrays.asList(
+ new FontConfig.Font(new File("test.ttc"), null, "foo",
+ new FontStyle(FONT_WEIGHT_NORMAL, FONT_SLANT_UPRIGHT), 0, "", null),
+ new FontConfig.Font(new File("test.ttc"), null, "test",
+ new FontStyle(FONT_WEIGHT_NORMAL, FONT_SLANT_UPRIGHT), 1, "", null)),
+ LocaleList.getEmptyLocaleList(), VARIANT_DEFAULT)), "sans-serif");
+ FontConfig.NamedFamilyList family = readNamedFamily(xml);
assertThat(family).isEqualTo(expected);
}
@@ -396,4 +392,14 @@
parser.nextTag();
return FontListParser.readFamily(parser, "", null, true);
}
+
+ private FontConfig.NamedFamilyList readNamedFamily(String xml)
+ throws IOException, XmlPullParserException {
+ ByteArrayInputStream buffer = new ByteArrayInputStream(
+ xml.getBytes(StandardCharsets.UTF_8));
+ XmlPullParser parser = Xml.newPullParser();
+ parser.setInput(buffer, "UTF-8");
+ parser.nextTag();
+ return FontListParser.readNamedFamily(parser, "", null, true);
+ }
}
diff --git a/core/tests/coretests/src/android/graphics/TypefaceSystemFallbackTest.java b/core/tests/coretests/src/android/graphics/TypefaceSystemFallbackTest.java
index 3df2e90..a8a5059 100644
--- a/core/tests/coretests/src/android/graphics/TypefaceSystemFallbackTest.java
+++ b/core/tests/coretests/src/android/graphics/TypefaceSystemFallbackTest.java
@@ -28,6 +28,8 @@
import android.graphics.fonts.FontCustomizationParser;
import android.graphics.fonts.FontFamily;
import android.graphics.fonts.SystemFonts;
+import android.graphics.text.PositionedGlyphs;
+import android.graphics.text.TextRunShaper;
import android.text.FontConfig;
import android.util.ArrayMap;
@@ -64,6 +66,8 @@
"b3em.ttf", // Supports "a","b","c". The width of "b" is 3em, others are 1em.
"c3em.ttf", // Supports "a","b","c". The width of "c" is 3em, others are 1em.
"all2em.ttf", // Supports "a,","b","c". All of them have the same width of 2em.
+ "fallback.ttf", // SUpports all small alphabets.
+ "fallback_capital.ttf", // SUpports all capital alphabets.
"no_coverage.ttf", // This font doesn't support any characters.
};
private static final String TEST_FONTS_XML;
@@ -165,7 +169,10 @@
Map<String, File> updatableFontMap = new HashMap<>();
for (File file : new File(TEST_UPDATABLE_FONT_DIR).listFiles()) {
- updatableFontMap.put(file.getName(), file);
+ final String fileName = file.getName();
+ final int periodIndex = fileName.lastIndexOf(".");
+ final String psName = fileName.substring(0, periodIndex);
+ updatableFontMap.put(psName, file);
}
FontConfig fontConfig;
@@ -710,6 +717,47 @@
assertEquals(GLYPH_1EM_WIDTH, paint.measureText("c"), 0.0f);
}
+ private String getFontName(Paint paint, String text) {
+ PositionedGlyphs glyphs = TextRunShaper.shapeTextRun(
+ text, 0, text.length(), 0, text.length(), 0f, 0f, false, paint);
+ assertEquals(1, glyphs.glyphCount());
+ return glyphs.getFont(0).getFile().getName();
+ }
+
+ @Test
+ public void testBuildSystemFallback__Customization_new_named_familyList() {
+ final String xml = "<?xml version='1.0' encoding='UTF-8'?>"
+ + "<familyset>"
+ + " <family name='sans-serif'>"
+ + " <font weight='400' style='normal'>fallback_capital.ttf</font>"
+ + " </family>"
+ + "</familyset>";
+ final String oemXml = "<?xml version='1.0' encoding='UTF-8'?>"
+ + "<fonts-modification version='1'>"
+ + " <family-list customizationType='new-named-family' name='google-sans'>"
+ + " <family>"
+ + " <font weight='400' style='normal'>b3em.ttf</font>"
+ + " </family>"
+ + " <family>"
+ + " <font weight='400' style='normal'>fallback.ttf</font>"
+ + " </family>"
+ + " </family-list>"
+ + "</fonts-modification>";
+ final ArrayMap<String, Typeface> fontMap = new ArrayMap<>();
+ final ArrayMap<String, FontFamily[]> fallbackMap = new ArrayMap<>();
+
+ buildSystemFallback(xml, oemXml, fontMap, fallbackMap);
+
+ final Paint paint = new Paint();
+
+ Typeface testTypeface = fontMap.get("google-sans");
+ assertNotNull(testTypeface);
+ paint.setTypeface(testTypeface);
+ assertEquals("b3em.ttf", getFontName(paint, "a"));
+ assertEquals("fallback.ttf", getFontName(paint, "x"));
+ assertEquals("fallback_capital.ttf", getFontName(paint, "A"));
+ }
+
@Test
public void testBuildSystemFallback__Customization_new_named_family_override() {
final String xml = "<?xml version='1.0' encoding='UTF-8'?>"
diff --git a/core/tests/coretests/src/android/view/accessibility/AccessibilityServiceConnectionImpl.java b/core/tests/coretests/src/android/view/accessibility/AccessibilityServiceConnectionImpl.java
index 6443ac18..756888f 100644
--- a/core/tests/coretests/src/android/view/accessibility/AccessibilityServiceConnectionImpl.java
+++ b/core/tests/coretests/src/android/view/accessibility/AccessibilityServiceConnectionImpl.java
@@ -226,4 +226,7 @@
@Override
public void attachAccessibilityOverlayToDisplay(int displayId, SurfaceControl sc) {}
+
+ @Override
+ public void attachAccessibilityOverlayToWindow(int accessibilityWindowId, SurfaceControl sc) {}
}
diff --git a/data/etc/com.android.launcher3.xml b/data/etc/com.android.launcher3.xml
index 36a5134..5616d1d 100644
--- a/data/etc/com.android.launcher3.xml
+++ b/data/etc/com.android.launcher3.xml
@@ -25,5 +25,6 @@
<permission name="android.permission.START_TASKS_FROM_RECENTS"/>
<permission name="android.permission.STATUS_BAR"/>
<permission name="android.permission.STOP_APP_SWITCHES"/>
+ <permission name="android.permission.ACCESS_SHORTCUTS"/>
</privapp-permissions>
</permissions>
diff --git a/graphics/java/android/graphics/FontListParser.java b/graphics/java/android/graphics/FontListParser.java
index 4bb16c6..674246a 100644
--- a/graphics/java/android/graphics/FontListParser.java
+++ b/graphics/java/android/graphics/FontListParser.java
@@ -16,6 +16,8 @@
package android.graphics;
+import static android.text.FontConfig.NamedFamilyList;
+
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.compat.annotation.UnsupportedAppUsage;
@@ -36,6 +38,7 @@
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
+import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Set;
@@ -46,6 +49,7 @@
* @hide
*/
public class FontListParser {
+ private static final String TAG = "FontListParser";
// XML constants for FontFamily.
private static final String ATTR_NAME = "name";
@@ -148,27 +152,60 @@
boolean allowNonExistingFile)
throws XmlPullParserException, IOException {
List<FontConfig.FontFamily> families = new ArrayList<>();
+ List<FontConfig.NamedFamilyList> resultNamedFamilies = new ArrayList<>();
List<FontConfig.Alias> aliases = new ArrayList<>(customization.getAdditionalAliases());
- Map<String, FontConfig.FontFamily> oemNamedFamilies =
+ Map<String, NamedFamilyList> oemNamedFamilies =
customization.getAdditionalNamedFamilies();
+ boolean firstFamily = true;
parser.require(XmlPullParser.START_TAG, null, "familyset");
while (keepReading(parser)) {
if (parser.getEventType() != XmlPullParser.START_TAG) continue;
String tag = parser.getName();
if (tag.equals("family")) {
- FontConfig.FontFamily family = readFamily(parser, fontDir, updatableFontMap,
- allowNonExistingFile);
- if (family == null) {
+ final String name = parser.getAttributeValue(null, "name");
+ if (name == null) {
+ FontConfig.FontFamily family = readFamily(parser, fontDir, updatableFontMap,
+ allowNonExistingFile);
+ if (family == null) {
+ continue;
+ }
+ families.add(family);
+
+ } else {
+ FontConfig.NamedFamilyList namedFamilyList = readNamedFamily(
+ parser, fontDir, updatableFontMap, allowNonExistingFile);
+ if (namedFamilyList == null) {
+ continue;
+ }
+ if (!oemNamedFamilies.containsKey(name)) {
+ // The OEM customization overrides system named family. Skip if OEM
+ // customization XML defines the same named family.
+ resultNamedFamilies.add(namedFamilyList);
+ }
+ if (firstFamily) {
+ // The first font family is used as a fallback family as well.
+ families.addAll(namedFamilyList.getFamilies());
+ }
+ }
+ firstFamily = false;
+ } else if (tag.equals("family-list")) {
+ FontConfig.NamedFamilyList namedFamilyList = readNamedFamilyList(
+ parser, fontDir, updatableFontMap, allowNonExistingFile);
+ if (namedFamilyList == null) {
continue;
}
- String name = family.getName();
- if (name == null || !oemNamedFamilies.containsKey(name)) {
+ if (!oemNamedFamilies.containsKey(namedFamilyList.getName())) {
// The OEM customization overrides system named family. Skip if OEM
// customization XML defines the same named family.
- families.add(family);
+ resultNamedFamilies.add(namedFamilyList);
}
+ if (firstFamily) {
+ // The first font family is used as a fallback family as well.
+ families.addAll(namedFamilyList.getFamilies());
+ }
+ firstFamily = false;
} else if (tag.equals("alias")) {
aliases.add(readAlias(parser));
} else {
@@ -176,12 +213,12 @@
}
}
- families.addAll(oemNamedFamilies.values());
+ resultNamedFamilies.addAll(oemNamedFamilies.values());
// Filters aliases that point to non-existing families.
Set<String> namedFamilies = new ArraySet<>();
- for (int i = 0; i < families.size(); ++i) {
- String name = families.get(i).getName();
+ for (int i = 0; i < resultNamedFamilies.size(); ++i) {
+ String name = resultNamedFamilies.get(i).getName();
if (name != null) {
namedFamilies.add(name);
}
@@ -194,7 +231,8 @@
}
}
- return new FontConfig(families, filtered, lastModifiedDate, configVersion);
+ return new FontConfig(families, filtered, resultNamedFamilies, lastModifiedDate,
+ configVersion);
}
private static boolean keepReading(XmlPullParser parser)
@@ -215,7 +253,6 @@
public static @Nullable FontConfig.FontFamily readFamily(XmlPullParser parser, String fontDir,
@Nullable Map<String, File> updatableFontMap, boolean allowNonExistingFile)
throws XmlPullParserException, IOException {
- final String name = parser.getAttributeValue(null, "name");
final String lang = parser.getAttributeValue("", "lang");
final String variant = parser.getAttributeValue(null, "variant");
final String ignore = parser.getAttributeValue(null, "ignore");
@@ -246,7 +283,68 @@
if (skip || fonts.isEmpty()) {
return null;
}
- return new FontConfig.FontFamily(fonts, name, LocaleList.forLanguageTags(lang), intVariant);
+ return new FontConfig.FontFamily(fonts, LocaleList.forLanguageTags(lang), intVariant);
+ }
+
+ private static void throwIfAttributeExists(String attrName, XmlPullParser parser) {
+ if (parser.getAttributeValue(null, attrName) != null) {
+ throw new IllegalArgumentException(attrName + " cannot be used in FontFamily inside "
+ + " family or family-list with name attribute.");
+ }
+ }
+
+ /**
+ * Read a font family with name attribute as a single element family-list element.
+ */
+ public static @Nullable FontConfig.NamedFamilyList readNamedFamily(
+ @NonNull XmlPullParser parser, @NonNull String fontDir,
+ @Nullable Map<String, File> updatableFontMap, boolean allowNonExistingFile)
+ throws XmlPullParserException, IOException {
+ final String name = parser.getAttributeValue(null, "name");
+ throwIfAttributeExists("lang", parser);
+ throwIfAttributeExists("variant", parser);
+ throwIfAttributeExists("ignore", parser);
+
+ final FontConfig.FontFamily family = readFamily(parser, fontDir, updatableFontMap,
+ allowNonExistingFile);
+ if (family == null) {
+ return null;
+ }
+ return new NamedFamilyList(Collections.singletonList(family), name);
+ }
+
+ /**
+ * Read a family-list element
+ */
+ public static @Nullable FontConfig.NamedFamilyList readNamedFamilyList(
+ @NonNull XmlPullParser parser, @NonNull String fontDir,
+ @Nullable Map<String, File> updatableFontMap, boolean allowNonExistingFile)
+ throws XmlPullParserException, IOException {
+ final String name = parser.getAttributeValue(null, "name");
+ final List<FontConfig.FontFamily> familyList = new ArrayList<>();
+ while (keepReading(parser)) {
+ if (parser.getEventType() != XmlPullParser.START_TAG) continue;
+ final String tag = parser.getName();
+ if (tag.equals("family")) {
+ throwIfAttributeExists("name", parser);
+ throwIfAttributeExists("lang", parser);
+ throwIfAttributeExists("variant", parser);
+ throwIfAttributeExists("ignore", parser);
+
+ final FontConfig.FontFamily family = readFamily(parser, fontDir, updatableFontMap,
+ allowNonExistingFile);
+ if (family != null) {
+ familyList.add(family);
+ }
+ } else {
+ skip(parser);
+ }
+ }
+
+ if (familyList.isEmpty()) {
+ return null;
+ }
+ return new FontConfig.NamedFamilyList(familyList, name);
}
/** Matches leading and trailing XML whitespace. */
diff --git a/graphics/java/android/graphics/ImageFormat.java b/graphics/java/android/graphics/ImageFormat.java
index 68f2927..88373e8 100644
--- a/graphics/java/android/graphics/ImageFormat.java
+++ b/graphics/java/android/graphics/ImageFormat.java
@@ -60,7 +60,8 @@
RAW_DEPTH,
RAW_DEPTH10,
PRIVATE,
- HEIC
+ HEIC,
+ JPEG_R
})
public @interface Format {
}
@@ -258,6 +259,14 @@
public static final int DEPTH_JPEG = 0x69656963;
/**
+ * Compressed JPEG format that includes an embedded recovery map.
+ *
+ * <p>JPEG compressed main image along with XMP embedded recovery map
+ * following ISO TBD.</p>
+ */
+ public static final int JPEG_R = 0x1005;
+
+ /**
* <p>Multi-plane Android YUV 420 format</p>
*
* <p>This format is a generic YCbCr format, capable of describing any 4:2:0
@@ -886,6 +895,7 @@
case Y8:
case DEPTH_JPEG:
case HEIC:
+ case JPEG_R:
return true;
}
diff --git a/graphics/java/android/graphics/fonts/FontCustomizationParser.java b/graphics/java/android/graphics/fonts/FontCustomizationParser.java
index df47f73..b458dd9 100644
--- a/graphics/java/android/graphics/fonts/FontCustomizationParser.java
+++ b/graphics/java/android/graphics/fonts/FontCustomizationParser.java
@@ -17,7 +17,7 @@
package android.graphics.fonts;
import static android.text.FontConfig.Alias;
-import static android.text.FontConfig.FontFamily;
+import static android.text.FontConfig.NamedFamilyList;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -42,11 +42,14 @@
* @hide
*/
public class FontCustomizationParser {
+ private static final String TAG = "FontCustomizationParser";
+
/**
* Represents a customization XML
*/
public static class Result {
- private final Map<String, FontFamily> mAdditionalNamedFamilies;
+ private final Map<String, NamedFamilyList> mAdditionalNamedFamilies;
+
private final List<Alias> mAdditionalAliases;
public Result() {
@@ -54,13 +57,13 @@
mAdditionalAliases = Collections.emptyList();
}
- public Result(Map<String, FontFamily> additionalNamedFamilies,
+ public Result(Map<String, NamedFamilyList> additionalNamedFamilies,
List<Alias> additionalAliases) {
mAdditionalNamedFamilies = additionalNamedFamilies;
mAdditionalAliases = additionalAliases;
}
- public Map<String, FontFamily> getAdditionalNamedFamilies() {
+ public Map<String, NamedFamilyList> getAdditionalNamedFamilies() {
return mAdditionalNamedFamilies;
}
@@ -85,20 +88,24 @@
return readFamilies(parser, fontDir, updatableFontMap);
}
- private static Map<String, FontFamily> validateAndTransformToMap(List<FontFamily> families) {
- HashMap<String, FontFamily> namedFamily = new HashMap<>();
+ private static Result validateAndTransformToResult(
+ List<NamedFamilyList> families, List<Alias> aliases) {
+ HashMap<String, NamedFamilyList> namedFamily = new HashMap<>();
for (int i = 0; i < families.size(); ++i) {
- final FontFamily family = families.get(i);
+ final NamedFamilyList family = families.get(i);
final String name = family.getName();
- if (name == null) {
- throw new IllegalArgumentException("new-named-family requires name attribute");
- }
- if (namedFamily.put(name, family) != null) {
+ if (name != null) {
+ if (namedFamily.put(name, family) != null) {
+ throw new IllegalArgumentException(
+ "new-named-family requires unique name attribute");
+ }
+ } else {
throw new IllegalArgumentException(
- "new-named-family requires unique name attribute");
+ "new-named-family requires name attribute or new-default-fallback-family"
+ + "requires fallackTarget attribute");
}
}
- return namedFamily;
+ return new Result(namedFamily, aliases);
}
private static Result readFamilies(
@@ -106,7 +113,7 @@
@NonNull String fontDir,
@Nullable Map<String, File> updatableFontMap
) throws XmlPullParserException, IOException {
- List<FontFamily> families = new ArrayList<>();
+ List<NamedFamilyList> families = new ArrayList<>();
List<Alias> aliases = new ArrayList<>();
parser.require(XmlPullParser.START_TAG, null, "fonts-modification");
while (parser.next() != XmlPullParser.END_TAG) {
@@ -114,19 +121,21 @@
String tag = parser.getName();
if (tag.equals("family")) {
readFamily(parser, fontDir, families, updatableFontMap);
+ } else if (tag.equals("family-list")) {
+ readFamilyList(parser, fontDir, families, updatableFontMap);
} else if (tag.equals("alias")) {
aliases.add(FontListParser.readAlias(parser));
} else {
FontListParser.skip(parser);
}
}
- return new Result(validateAndTransformToMap(families), aliases);
+ return validateAndTransformToResult(families, aliases);
}
private static void readFamily(
@NonNull XmlPullParser parser,
@NonNull String fontDir,
- @NonNull List<FontFamily> out,
+ @NonNull List<NamedFamilyList> out,
@Nullable Map<String, File> updatableFontMap)
throws XmlPullParserException, IOException {
final String customizationType = parser.getAttributeValue(null, "customizationType");
@@ -134,7 +143,28 @@
throw new IllegalArgumentException("customizationType must be specified");
}
if (customizationType.equals("new-named-family")) {
- FontFamily fontFamily = FontListParser.readFamily(
+ NamedFamilyList fontFamily = FontListParser.readNamedFamily(
+ parser, fontDir, updatableFontMap, false);
+ if (fontFamily != null) {
+ out.add(fontFamily);
+ }
+ } else {
+ throw new IllegalArgumentException("Unknown customizationType=" + customizationType);
+ }
+ }
+
+ private static void readFamilyList(
+ @NonNull XmlPullParser parser,
+ @NonNull String fontDir,
+ @NonNull List<NamedFamilyList> out,
+ @Nullable Map<String, File> updatableFontMap)
+ throws XmlPullParserException, IOException {
+ final String customizationType = parser.getAttributeValue(null, "customizationType");
+ if (customizationType == null) {
+ throw new IllegalArgumentException("customizationType must be specified");
+ }
+ if (customizationType.equals("new-named-family")) {
+ NamedFamilyList fontFamily = FontListParser.readNamedFamilyList(
parser, fontDir, updatableFontMap, false);
if (fontFamily != null) {
out.add(fontFamily);
diff --git a/graphics/java/android/graphics/fonts/FontFamily.java b/graphics/java/android/graphics/fonts/FontFamily.java
index a771a6e..bf79b1b 100644
--- a/graphics/java/android/graphics/fonts/FontFamily.java
+++ b/graphics/java/android/graphics/fonts/FontFamily.java
@@ -114,17 +114,19 @@
* @return a font family
*/
public @NonNull FontFamily build() {
- return build("", FontConfig.FontFamily.VARIANT_DEFAULT, true /* isCustomFallback */);
+ return build("", FontConfig.FontFamily.VARIANT_DEFAULT, true /* isCustomFallback */,
+ false /* isDefaultFallback */);
}
/** @hide */
public @NonNull FontFamily build(@NonNull String langTags, int variant,
- boolean isCustomFallback) {
+ boolean isCustomFallback, boolean isDefaultFallback) {
final long builderPtr = nInitBuilder();
for (int i = 0; i < mFonts.size(); ++i) {
nAddFont(builderPtr, mFonts.get(i).getNativePtr());
}
- final long ptr = nBuild(builderPtr, langTags, variant, isCustomFallback);
+ final long ptr = nBuild(builderPtr, langTags, variant, isCustomFallback,
+ isDefaultFallback);
final FontFamily family = new FontFamily(ptr);
sFamilyRegistory.registerNativeAllocation(family, ptr);
return family;
@@ -138,7 +140,7 @@
@CriticalNative
private static native void nAddFont(long builderPtr, long fontPtr);
private static native long nBuild(long builderPtr, String langTags, int variant,
- boolean isCustomFallback);
+ boolean isCustomFallback, boolean isDefaultFallback);
@CriticalNative
private static native long nGetReleaseNativeFamily();
}
diff --git a/graphics/java/android/graphics/fonts/SystemFonts.java b/graphics/java/android/graphics/fonts/SystemFonts.java
index 6278c0e..ec8b2d6 100644
--- a/graphics/java/android/graphics/fonts/SystemFonts.java
+++ b/graphics/java/android/graphics/fonts/SystemFonts.java
@@ -22,6 +22,7 @@
import android.graphics.Typeface;
import android.text.FontConfig;
import android.util.ArrayMap;
+import android.util.ArraySet;
import android.util.Log;
import com.android.internal.annotations.GuardedBy;
@@ -92,9 +93,8 @@
}
private static void pushFamilyToFallback(@NonNull FontConfig.FontFamily xmlFamily,
- @NonNull ArrayMap<String, ArrayList<FontFamily>> fallbackMap,
+ @NonNull ArrayMap<String, NativeFamilyListSet> fallbackMap,
@NonNull Map<String, ByteBuffer> cache) {
-
final String languageTags = xmlFamily.getLocaleList().toLanguageTags();
final int variant = xmlFamily.getVariant();
@@ -118,26 +118,29 @@
}
final FontFamily defaultFamily = defaultFonts.isEmpty() ? null : createFontFamily(
- xmlFamily.getName(), defaultFonts, languageTags, variant, cache);
+ defaultFonts, languageTags, variant, false, cache);
// Insert family into fallback map.
for (int i = 0; i < fallbackMap.size(); i++) {
- String name = fallbackMap.keyAt(i);
+ final String name = fallbackMap.keyAt(i);
+ final NativeFamilyListSet familyListSet = fallbackMap.valueAt(i);
+ if (familyListSet.seenXmlFamilies.contains(xmlFamily)) {
+ continue;
+ } else {
+ familyListSet.seenXmlFamilies.add(xmlFamily);
+ }
final ArrayList<FontConfig.Font> fallback = specificFallbackFonts.get(name);
if (fallback == null) {
- String familyName = xmlFamily.getName();
- if (defaultFamily != null
- // do not add myself to the fallback chain.
- && (familyName == null || !familyName.equals(name))) {
- fallbackMap.valueAt(i).add(defaultFamily);
+ if (defaultFamily != null) {
+ familyListSet.familyList.add(defaultFamily);
}
} else {
- final FontFamily family = createFontFamily(
- xmlFamily.getName(), fallback, languageTags, variant, cache);
+ final FontFamily family = createFontFamily(fallback, languageTags, variant, false,
+ cache);
if (family != null) {
- fallbackMap.valueAt(i).add(family);
+ familyListSet.familyList.add(family);
} else if (defaultFamily != null) {
- fallbackMap.valueAt(i).add(defaultFamily);
+ familyListSet.familyList.add(defaultFamily);
} else {
// There is no valid for for default fallback. Ignore.
}
@@ -145,10 +148,11 @@
}
}
- private static @Nullable FontFamily createFontFamily(@NonNull String familyName,
+ private static @Nullable FontFamily createFontFamily(
@NonNull List<FontConfig.Font> fonts,
@NonNull String languageTags,
@FontConfig.FontFamily.Variant int variant,
+ boolean isDefaultFallback,
@NonNull Map<String, ByteBuffer> cache) {
if (fonts.size() == 0) {
return null;
@@ -188,23 +192,30 @@
b.addFont(font);
}
}
- return b == null ? null : b.build(languageTags, variant, false /* isCustomFallback */);
+ return b == null ? null : b.build(languageTags, variant, false /* isCustomFallback */,
+ isDefaultFallback);
}
- private static void appendNamedFamily(@NonNull FontConfig.FontFamily xmlFamily,
+ private static void appendNamedFamilyList(@NonNull FontConfig.NamedFamilyList namedFamilyList,
@NonNull ArrayMap<String, ByteBuffer> bufferCache,
- @NonNull ArrayMap<String, ArrayList<FontFamily>> fallbackListMap) {
- final String familyName = xmlFamily.getName();
- final FontFamily family = createFontFamily(
- familyName, xmlFamily.getFontList(),
- xmlFamily.getLocaleList().toLanguageTags(), xmlFamily.getVariant(),
- bufferCache);
- if (family == null) {
- return;
+ @NonNull ArrayMap<String, NativeFamilyListSet> fallbackListMap) {
+ final String familyName = namedFamilyList.getName();
+ final NativeFamilyListSet familyListSet = new NativeFamilyListSet();
+ final List<FontConfig.FontFamily> xmlFamilies = namedFamilyList.getFamilies();
+ for (int i = 0; i < xmlFamilies.size(); ++i) {
+ FontConfig.FontFamily xmlFamily = xmlFamilies.get(i);
+ final FontFamily family = createFontFamily(
+ xmlFamily.getFontList(),
+ xmlFamily.getLocaleList().toLanguageTags(), xmlFamily.getVariant(),
+ true, // named family is always default
+ bufferCache);
+ if (family == null) {
+ return;
+ }
+ familyListSet.familyList.add(family);
+ familyListSet.seenXmlFamilies.add(xmlFamily);
}
- final ArrayList<FontFamily> fallback = new ArrayList<>();
- fallback.add(family);
- fallbackListMap.put(familyName, fallback);
+ fallbackListMap.put(familyName, familyListSet);
}
/**
@@ -245,10 +256,12 @@
updatableFontMap, lastModifiedDate, configVersion);
} catch (IOException e) {
Log.e(TAG, "Failed to open/read system font configurations.", e);
- return new FontConfig(Collections.emptyList(), Collections.emptyList(), 0, 0);
+ return new FontConfig(Collections.emptyList(), Collections.emptyList(),
+ Collections.emptyList(), 0, 0);
} catch (XmlPullParserException e) {
Log.e(TAG, "Failed to parse the system font configuration.", e);
- return new FontConfig(Collections.emptyList(), Collections.emptyList(), 0, 0);
+ return new FontConfig(Collections.emptyList(), Collections.emptyList(),
+ Collections.emptyList(), 0, 0);
}
}
@@ -261,37 +274,36 @@
return buildSystemFallback(fontConfig, new ArrayMap<>());
}
+ private static final class NativeFamilyListSet {
+ public List<FontFamily> familyList = new ArrayList<>();
+ public Set<FontConfig.FontFamily> seenXmlFamilies = new ArraySet<>();
+ }
+
/** @hide */
@VisibleForTesting
public static Map<String, FontFamily[]> buildSystemFallback(FontConfig fontConfig,
ArrayMap<String, ByteBuffer> outBufferCache) {
- final Map<String, FontFamily[]> fallbackMap = new ArrayMap<>();
- final List<FontConfig.FontFamily> xmlFamilies = fontConfig.getFontFamilies();
- final ArrayMap<String, ArrayList<FontFamily>> fallbackListMap = new ArrayMap<>();
- // First traverse families which have a 'name' attribute to create fallback map.
- for (final FontConfig.FontFamily xmlFamily : xmlFamilies) {
- final String familyName = xmlFamily.getName();
- if (familyName == null) {
- continue;
- }
- appendNamedFamily(xmlFamily, outBufferCache, fallbackListMap);
+ final ArrayMap<String, NativeFamilyListSet> fallbackListMap = new ArrayMap<>();
+
+ final List<FontConfig.NamedFamilyList> namedFamilies = fontConfig.getNamedFamilyLists();
+ for (int i = 0; i < namedFamilies.size(); ++i) {
+ FontConfig.NamedFamilyList namedFamilyList = namedFamilies.get(i);
+ appendNamedFamilyList(namedFamilyList, outBufferCache, fallbackListMap);
}
- // Then, add fallback fonts to the each fallback map.
+ // Then, add fallback fonts to the fallback map.
+ final List<FontConfig.FontFamily> xmlFamilies = fontConfig.getFontFamilies();
for (int i = 0; i < xmlFamilies.size(); i++) {
final FontConfig.FontFamily xmlFamily = xmlFamilies.get(i);
- // The first family (usually the sans-serif family) is always placed immediately
- // after the primary family in the fallback.
- if (i == 0 || xmlFamily.getName() == null) {
- pushFamilyToFallback(xmlFamily, fallbackListMap, outBufferCache);
- }
+ pushFamilyToFallback(xmlFamily, fallbackListMap, outBufferCache);
}
// Build the font map and fallback map.
+ final Map<String, FontFamily[]> fallbackMap = new ArrayMap<>();
for (int i = 0; i < fallbackListMap.size(); i++) {
final String fallbackName = fallbackListMap.keyAt(i);
- final List<FontFamily> familyList = fallbackListMap.valueAt(i);
+ final List<FontFamily> familyList = fallbackListMap.valueAt(i).familyList;
fallbackMap.put(fallbackName, familyList.toArray(new FontFamily[0]));
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java
index 04d62f6..abe42ee 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java
@@ -2922,8 +2922,10 @@
.withEndActions(() -> {
View child = mManageMenu.getChildAt(0);
child.requestAccessibilityFocus();
- // Update the AV's obscured touchable region for the new visibility state.
- mExpandedBubble.getExpandedView().updateObscuredTouchableRegion();
+ if (mExpandedBubble != null && mExpandedBubble.getExpandedView() != null) {
+ // Update the AV's obscured touchable region for the new state.
+ mExpandedBubble.getExpandedView().updateObscuredTouchableRegion();
+ }
})
.start();
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipAccessibilityInteractionConnection.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipAccessibilityInteractionConnection.java
index 1f7a7fc..4a06d84 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipAccessibilityInteractionConnection.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipAccessibilityInteractionConnection.java
@@ -24,6 +24,7 @@
import android.os.Bundle;
import android.os.RemoteException;
import android.view.MagnificationSpec;
+import android.view.SurfaceControl;
import android.view.accessibility.AccessibilityManager;
import android.view.accessibility.AccessibilityNodeInfo;
import android.view.accessibility.AccessibilityWindowInfo;
@@ -380,5 +381,8 @@
public void notifyOutsideTouch() throws RemoteException {
// Do nothing
}
- }
+
+ @Override
+ public void attachAccessibilityOverlayToWindow(SurfaceControl sc) {}
}
+}
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipBoundsAlgorithm.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipBoundsAlgorithm.java
index a4b7033..31490e4 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipBoundsAlgorithm.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipBoundsAlgorithm.java
@@ -22,7 +22,6 @@
import static android.view.KeyEvent.KEYCODE_DPAD_UP;
import static com.android.wm.shell.pip.tv.TvPipBoundsState.ORIENTATION_HORIZONTAL;
-import static com.android.wm.shell.pip.tv.TvPipBoundsState.ORIENTATION_UNDETERMINED;
import static com.android.wm.shell.pip.tv.TvPipBoundsState.ORIENTATION_VERTICAL;
import android.content.Context;
@@ -63,7 +62,8 @@
@NonNull TvPipBoundsState tvPipBoundsState,
@NonNull PipSnapAlgorithm pipSnapAlgorithm) {
super(context, tvPipBoundsState, pipSnapAlgorithm,
- new PipKeepClearAlgorithm() {});
+ new PipKeepClearAlgorithm() {
+ });
this.mTvPipBoundsState = tvPipBoundsState;
this.mKeepClearAlgorithm = new TvPipKeepClearAlgorithm();
reloadResources(context);
@@ -98,7 +98,7 @@
&& mTvPipBoundsState.getDesiredTvExpandedAspectRatio() != 0
&& !mTvPipBoundsState.isTvPipManuallyCollapsed();
if (isPipExpanded) {
- updateGravityOnExpandToggled(Gravity.NO_GRAVITY, true);
+ updateGravityOnExpansionToggled(/* expanding= */ true);
}
mTvPipBoundsState.setTvPipExpanded(isPipExpanded);
return adjustBoundsForTemporaryDecor(getTvPipPlacement().getBounds());
@@ -172,135 +172,85 @@
return placement;
}
- /**
- * @return previous gravity if it is to be saved, or {@link Gravity#NO_GRAVITY} if not.
- */
- int updateGravityOnExpandToggled(int previousGravity, boolean expanding) {
+ void updateGravityOnExpansionToggled(boolean expanding) {
ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
- "%s: updateGravityOnExpandToggled(), expanding: %b"
- + ", mOrientation: %d, previous gravity: %s",
- TAG, expanding, mTvPipBoundsState.getTvFixedPipOrientation(),
- Gravity.toString(previousGravity));
+ "%s: updateGravity, expanding: %b, fixedExpandedOrientation: %d",
+ TAG, expanding, mTvPipBoundsState.getTvFixedPipOrientation());
- if (!mTvPipBoundsState.isTvExpandedPipSupported()) {
- return Gravity.NO_GRAVITY;
- }
+ int currentX = mTvPipBoundsState.getTvPipGravity() & Gravity.HORIZONTAL_GRAVITY_MASK;
+ int currentY = mTvPipBoundsState.getTvPipGravity() & Gravity.VERTICAL_GRAVITY_MASK;
+ int previousCollapsedX = mTvPipBoundsState.getTvPipPreviousCollapsedGravity()
+ & Gravity.HORIZONTAL_GRAVITY_MASK;
+ int previousCollapsedY = mTvPipBoundsState.getTvPipPreviousCollapsedGravity()
+ & Gravity.VERTICAL_GRAVITY_MASK;
- if (expanding && mTvPipBoundsState.getTvFixedPipOrientation() == ORIENTATION_UNDETERMINED) {
- float expandedRatio = mTvPipBoundsState.getDesiredTvExpandedAspectRatio();
- if (expandedRatio == 0) {
- return Gravity.NO_GRAVITY;
- }
- if (expandedRatio < 1) {
- mTvPipBoundsState.setTvFixedPipOrientation(ORIENTATION_VERTICAL);
- } else {
- mTvPipBoundsState.setTvFixedPipOrientation(ORIENTATION_HORIZONTAL);
- }
- }
-
- int gravityToSave = Gravity.NO_GRAVITY;
- int currentGravity = mTvPipBoundsState.getTvPipGravity();
int updatedGravity;
-
if (expanding) {
- // save collapsed gravity
- gravityToSave = mTvPipBoundsState.getTvPipGravity();
+ // Save collapsed gravity.
+ mTvPipBoundsState.setTvPipPreviousCollapsedGravity(mTvPipBoundsState.getTvPipGravity());
if (mTvPipBoundsState.getTvFixedPipOrientation() == ORIENTATION_HORIZONTAL) {
- updatedGravity =
- Gravity.CENTER_HORIZONTAL | (currentGravity
- & Gravity.VERTICAL_GRAVITY_MASK);
+ updatedGravity = Gravity.CENTER_HORIZONTAL | currentY;
} else {
- updatedGravity =
- Gravity.CENTER_VERTICAL | (currentGravity
- & Gravity.HORIZONTAL_GRAVITY_MASK);
+ updatedGravity = currentX | Gravity.CENTER_VERTICAL;
}
} else {
- if (previousGravity != Gravity.NO_GRAVITY) {
- // The pip hasn't been moved since expanding,
- // go back to previous collapsed position.
- updatedGravity = previousGravity;
+ // Collapse to the edge that the user moved to before.
+ if (mTvPipBoundsState.getTvFixedPipOrientation() == ORIENTATION_HORIZONTAL) {
+ updatedGravity = previousCollapsedX | currentY;
} else {
- if (mTvPipBoundsState.getTvFixedPipOrientation() == ORIENTATION_HORIZONTAL) {
- updatedGravity =
- Gravity.RIGHT | (currentGravity & Gravity.VERTICAL_GRAVITY_MASK);
- } else {
- updatedGravity =
- Gravity.BOTTOM | (currentGravity & Gravity.HORIZONTAL_GRAVITY_MASK);
- }
+ updatedGravity = currentX | previousCollapsedY;
}
}
mTvPipBoundsState.setTvPipGravity(updatedGravity);
ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
"%s: new gravity: %s", TAG, Gravity.toString(updatedGravity));
-
- return gravityToSave;
}
/**
- * @return true if gravity changed
+ * @return true if the gravity changed
*/
boolean updateGravity(int keycode) {
ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
"%s: updateGravity, keycode: %d", TAG, keycode);
- // Check if position change is valid
+ // Check if position change is valid.
if (mTvPipBoundsState.isTvPipExpanded()) {
- int mOrientation = mTvPipBoundsState.getTvFixedPipOrientation();
- if (mOrientation == ORIENTATION_VERTICAL
+ int fixedOrientation = mTvPipBoundsState.getTvFixedPipOrientation();
+ if (fixedOrientation == ORIENTATION_VERTICAL
&& (keycode == KEYCODE_DPAD_UP || keycode == KEYCODE_DPAD_DOWN)
- || mOrientation == ORIENTATION_HORIZONTAL
+ || fixedOrientation == ORIENTATION_HORIZONTAL
&& (keycode == KEYCODE_DPAD_RIGHT || keycode == KEYCODE_DPAD_LEFT)) {
return false;
}
}
- int currentGravity = mTvPipBoundsState.getTvPipGravity();
- int updatedGravity;
- // First axis
+ int updatedX = mTvPipBoundsState.getTvPipGravity() & Gravity.HORIZONTAL_GRAVITY_MASK;
+ int updatedY = mTvPipBoundsState.getTvPipGravity() & Gravity.VERTICAL_GRAVITY_MASK;
+
switch (keycode) {
case KEYCODE_DPAD_UP:
- updatedGravity = Gravity.TOP;
+ updatedY = Gravity.TOP;
break;
case KEYCODE_DPAD_DOWN:
- updatedGravity = Gravity.BOTTOM;
+ updatedY = Gravity.BOTTOM;
break;
case KEYCODE_DPAD_LEFT:
- updatedGravity = Gravity.LEFT;
+ updatedX = Gravity.LEFT;
break;
case KEYCODE_DPAD_RIGHT:
- updatedGravity = Gravity.RIGHT;
+ updatedX = Gravity.RIGHT;
break;
default:
- updatedGravity = currentGravity;
+ // NOOP - unsupported keycode
}
- // Second axis
- switch (keycode) {
- case KEYCODE_DPAD_UP:
- case KEYCODE_DPAD_DOWN:
- if (mTvPipBoundsState.isTvPipExpanded()) {
- updatedGravity |= Gravity.CENTER_HORIZONTAL;
- } else {
- updatedGravity |= (currentGravity & Gravity.HORIZONTAL_GRAVITY_MASK);
- }
- break;
- case KEYCODE_DPAD_LEFT:
- case KEYCODE_DPAD_RIGHT:
- if (mTvPipBoundsState.isTvPipExpanded()) {
- updatedGravity |= Gravity.CENTER_VERTICAL;
- } else {
- updatedGravity |= (currentGravity & Gravity.VERTICAL_GRAVITY_MASK);
- }
- break;
- default:
- break;
- }
+ int updatedGravity = updatedX | updatedY;
- if (updatedGravity != currentGravity) {
+ if (updatedGravity != mTvPipBoundsState.getTvPipGravity()) {
mTvPipBoundsState.setTvPipGravity(updatedGravity);
ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
- "%s: new gravity: %s", TAG, Gravity.toString(updatedGravity));
+ "%s: updateGravity, new gravity: %s", TAG, Gravity.toString(updatedGravity));
return true;
}
return false;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipBoundsState.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipBoundsState.java
index 1651f89..9c7c0ae 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipBoundsState.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipBoundsState.java
@@ -27,6 +27,7 @@
import android.graphics.Insets;
import android.util.Size;
import android.view.Gravity;
+import android.view.View;
import com.android.wm.shell.pip.PipBoundsAlgorithm;
import com.android.wm.shell.pip.PipBoundsState;
@@ -52,24 +53,61 @@
public @interface Orientation {
}
- public static final int DEFAULT_TV_GRAVITY = Gravity.BOTTOM | Gravity.RIGHT;
+ private final Context mContext;
+
+ private int mDefaultGravity;
+ private int mTvPipGravity;
+ private int mPreviousCollapsedGravity;
+ private boolean mIsRtl;
private final boolean mIsTvExpandedPipSupported;
private boolean mIsTvPipExpanded;
private boolean mTvPipManuallyCollapsed;
private float mDesiredTvExpandedAspectRatio;
- private @Orientation int mTvFixedPipOrientation;
- private int mTvPipGravity;
- private @Nullable Size mTvExpandedSize;
- private @NonNull Insets mPipMenuPermanentDecorInsets = Insets.NONE;
- private @NonNull Insets mPipMenuTemporaryDecorInsets = Insets.NONE;
+ @Orientation
+ private int mTvFixedPipOrientation;
+ @Nullable
+ private Size mTvExpandedSize;
+ @NonNull
+ private Insets mPipMenuPermanentDecorInsets = Insets.NONE;
+ @NonNull
+ private Insets mPipMenuTemporaryDecorInsets = Insets.NONE;
public TvPipBoundsState(@NonNull Context context) {
super(context);
+ mContext = context;
+ updateDefaultGravity();
+ mPreviousCollapsedGravity = mDefaultGravity;
mIsTvExpandedPipSupported = context.getPackageManager().hasSystemFeature(
PackageManager.FEATURE_EXPANDED_PICTURE_IN_PICTURE);
}
+ public int getDefaultGravity() {
+ return mDefaultGravity;
+ }
+
+ private void updateDefaultGravity() {
+ boolean isRtl = mContext.getResources().getConfiguration().getLayoutDirection()
+ == View.LAYOUT_DIRECTION_RTL;
+ mDefaultGravity = Gravity.BOTTOM | (isRtl ? Gravity.LEFT : Gravity.RIGHT);
+
+ if (mIsRtl != isRtl) {
+ int prevGravityX = mPreviousCollapsedGravity & Gravity.HORIZONTAL_GRAVITY_MASK;
+ int prevGravityY = mPreviousCollapsedGravity & Gravity.VERTICAL_GRAVITY_MASK;
+ if ((prevGravityX & Gravity.RIGHT) == Gravity.RIGHT) {
+ mPreviousCollapsedGravity = Gravity.LEFT | prevGravityY;
+ } else if ((prevGravityX & Gravity.LEFT) == Gravity.LEFT) {
+ mPreviousCollapsedGravity = Gravity.RIGHT | prevGravityY;
+ }
+ }
+ mIsRtl = isRtl;
+ }
+
+ @Override
+ public void onConfigurationChanged() {
+ updateDefaultGravity();
+ }
+
/**
* Initialize states when first entering PiP.
*/
@@ -86,7 +124,8 @@
/** Resets the TV PiP state for a new activity. */
public void resetTvPipState() {
mTvFixedPipOrientation = ORIENTATION_UNDETERMINED;
- mTvPipGravity = DEFAULT_TV_GRAVITY;
+ mTvPipGravity = mDefaultGravity;
+ mPreviousCollapsedGravity = mDefaultGravity;
mTvPipManuallyCollapsed = false;
}
@@ -102,16 +141,23 @@
}
/** Set the PiP aspect ratio for the expanded PiP (TV) that is desired by the app. */
- public void setDesiredTvExpandedAspectRatio(float aspectRatio, boolean override) {
+ public void setDesiredTvExpandedAspectRatio(float expandedAspectRatio, boolean override) {
if (override || mTvFixedPipOrientation == ORIENTATION_UNDETERMINED) {
- mDesiredTvExpandedAspectRatio = aspectRatio;
resetTvPipState();
+ mDesiredTvExpandedAspectRatio = expandedAspectRatio;
+ if (expandedAspectRatio != 0) {
+ if (expandedAspectRatio > 1) {
+ mTvFixedPipOrientation = ORIENTATION_HORIZONTAL;
+ } else {
+ mTvFixedPipOrientation = ORIENTATION_VERTICAL;
+ }
+ }
return;
}
- if ((aspectRatio > 1 && mTvFixedPipOrientation == ORIENTATION_HORIZONTAL)
- || (aspectRatio <= 1 && mTvFixedPipOrientation == ORIENTATION_VERTICAL)
- || aspectRatio == 0) {
- mDesiredTvExpandedAspectRatio = aspectRatio;
+ if ((expandedAspectRatio > 1 && mTvFixedPipOrientation == ORIENTATION_HORIZONTAL)
+ || (expandedAspectRatio <= 1 && mTvFixedPipOrientation == ORIENTATION_VERTICAL)
+ || expandedAspectRatio == 0) {
+ mDesiredTvExpandedAspectRatio = expandedAspectRatio;
}
}
@@ -123,11 +169,6 @@
return mDesiredTvExpandedAspectRatio;
}
- /** Sets the orientation the expanded TV PiP activity has been fixed to. */
- public void setTvFixedPipOrientation(@Orientation int orientation) {
- mTvFixedPipOrientation = orientation;
- }
-
/** Returns the fixed orientation of the expanded PiP on TV. */
@Orientation
public int getTvFixedPipOrientation() {
@@ -144,6 +185,14 @@
return mTvPipGravity;
}
+ public void setTvPipPreviousCollapsedGravity(int gravity) {
+ mPreviousCollapsedGravity = gravity;
+ }
+
+ public int getTvPipPreviousCollapsedGravity() {
+ return mPreviousCollapsedGravity;
+ }
+
/** Sets whether the TV PiP is currently expanded. */
public void setTvPipExpanded(boolean expanded) {
mIsTvPipExpanded = expanded;
@@ -173,7 +222,8 @@
mPipMenuPermanentDecorInsets = permanentInsets;
}
- public @NonNull Insets getPipMenuPermanentDecorInsets() {
+ @NonNull
+ public Insets getPipMenuPermanentDecorInsets() {
return mPipMenuPermanentDecorInsets;
}
@@ -181,7 +231,8 @@
mPipMenuTemporaryDecorInsets = temporaryDecorInsets;
}
- public @NonNull Insets getPipMenuTemporaryDecorInsets() {
+ @NonNull
+ public Insets getPipMenuTemporaryDecorInsets() {
return mPipMenuTemporaryDecorInsets;
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipController.java
index 7671081..fd4fcff 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipController.java
@@ -18,6 +18,8 @@
import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED;
import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
+import static android.view.KeyEvent.KEYCODE_DPAD_LEFT;
+import static android.view.KeyEvent.KEYCODE_DPAD_RIGHT;
import android.annotation.IntDef;
import android.app.ActivityManager;
@@ -137,7 +139,6 @@
@State
private int mState = STATE_NO_PIP;
- private int mPreviousGravity = TvPipBoundsState.DEFAULT_TV_GRAVITY;
private int mPinnedTaskId = NONEXISTENT_TASK_ID;
// How long the shell will wait for the app to close the PiP if a custom action is set.
@@ -214,6 +215,7 @@
mTvPipBoundsState = tvPipBoundsState;
mTvPipBoundsState.setDisplayId(context.getDisplayId());
mTvPipBoundsState.setDisplayLayout(new DisplayLayout(context, context.getDisplay()));
+
mTvPipBoundsAlgorithm = tvPipBoundsAlgorithm;
mTvPipBoundsController = tvPipBoundsController;
mTvPipBoundsController.setListener(this);
@@ -243,7 +245,7 @@
private void onInit() {
mPipTransitionController.registerPipTransitionCallback(this);
- loadConfigurations();
+ reloadResources();
registerPipParamsChangedListener(mPipParamsChangedForwarder);
registerTaskStackListenerCallback(mTaskStackListener);
@@ -266,9 +268,38 @@
ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
"%s: onConfigurationChanged(), state=%s", TAG, stateToName(mState));
- loadConfigurations();
+ int previousDefaultGravityX = mTvPipBoundsState.getDefaultGravity()
+ & Gravity.HORIZONTAL_GRAVITY_MASK;
+
+ reloadResources();
+
mPipNotificationController.onConfigurationChanged();
mTvPipBoundsAlgorithm.onConfigurationChanged(mContext);
+ mTvPipBoundsState.onConfigurationChanged();
+
+ int defaultGravityX = mTvPipBoundsState.getDefaultGravity()
+ & Gravity.HORIZONTAL_GRAVITY_MASK;
+ if (isPipShown() && previousDefaultGravityX != defaultGravityX) {
+ movePipToOppositeSide();
+ }
+ }
+
+ private void reloadResources() {
+ final Resources res = mContext.getResources();
+ mResizeAnimationDuration = res.getInteger(R.integer.config_pipResizeAnimationDuration);
+ mPipForceCloseDelay = res.getInteger(R.integer.config_pipForceCloseDelay);
+ mEduTextWindowExitAnimationDuration =
+ res.getInteger(R.integer.pip_edu_text_window_exit_animation_duration);
+ }
+
+ private void movePipToOppositeSide() {
+ ProtoLog.i(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "%s: movePipToOppositeSide", TAG);
+ if ((mTvPipBoundsState.getTvPipGravity() & Gravity.RIGHT) == Gravity.RIGHT) {
+ movePip(KEYCODE_DPAD_LEFT);
+ } else if ((mTvPipBoundsState.getTvPipGravity() & Gravity.LEFT) == Gravity.LEFT) {
+ movePip(KEYCODE_DPAD_RIGHT);
+ }
}
/**
@@ -332,11 +363,7 @@
ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
"%s: togglePipExpansion()", TAG);
boolean expanding = !mTvPipBoundsState.isTvPipExpanded();
- int saveGravity = mTvPipBoundsAlgorithm
- .updateGravityOnExpandToggled(mPreviousGravity, expanding);
- if (saveGravity != Gravity.NO_GRAVITY) {
- mPreviousGravity = saveGravity;
- }
+ mTvPipBoundsAlgorithm.updateGravityOnExpansionToggled(expanding);
mTvPipBoundsState.setTvPipManuallyCollapsed(!expanding);
mTvPipBoundsState.setTvPipExpanded(expanding);
@@ -347,7 +374,6 @@
public void movePip(int keycode) {
if (mTvPipBoundsAlgorithm.updateGravity(keycode)) {
mTvPipMenuController.updateGravity(mTvPipBoundsState.getTvPipGravity());
- mPreviousGravity = Gravity.NO_GRAVITY;
updatePinnedStackBounds();
} else {
ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
@@ -356,15 +382,6 @@
}
@Override
- public int getPipGravity() {
- return mTvPipBoundsState.getTvPipGravity();
- }
-
- public int getOrientation() {
- return mTvPipBoundsState.getTvFixedPipOrientation();
- }
-
- @Override
public void onKeepClearAreasChanged(int displayId, Set<Rect> restricted,
Set<Rect> unrestricted) {
if (mTvPipBoundsState.getDisplayId() == displayId) {
@@ -516,14 +533,6 @@
mState = state;
}
- private void loadConfigurations() {
- final Resources res = mContext.getResources();
- mResizeAnimationDuration = res.getInteger(R.integer.config_pipResizeAnimationDuration);
- mPipForceCloseDelay = res.getInteger(R.integer.config_pipForceCloseDelay);
- mEduTextWindowExitAnimationDuration =
- res.getInteger(R.integer.pip_edu_text_window_exit_animation_duration);
- }
-
private void registerTaskStackListenerCallback(TaskStackListenerImpl taskStackListener) {
taskStackListener.addListener(new TaskStackListenerCallback() {
@Override
@@ -596,11 +605,7 @@
// 2) PiP is expanded, but expanded PiP was disabled
// --> collapse PiP
if (mTvPipBoundsState.isTvPipExpanded() && ratio == 0) {
- int saveGravity = mTvPipBoundsAlgorithm
- .updateGravityOnExpandToggled(mPreviousGravity, false);
- if (saveGravity != Gravity.NO_GRAVITY) {
- mPreviousGravity = saveGravity;
- }
+ mTvPipBoundsAlgorithm.updateGravityOnExpansionToggled(/* expanding= */ false);
mTvPipBoundsState.setTvPipExpanded(false);
updatePinnedStackBounds();
}
@@ -610,11 +615,7 @@
if (!mTvPipBoundsState.isTvPipExpanded() && ratio != 0
&& !mTvPipBoundsState.isTvPipManuallyCollapsed()) {
mTvPipBoundsAlgorithm.updateExpandedPipSize();
- int saveGravity = mTvPipBoundsAlgorithm
- .updateGravityOnExpandToggled(mPreviousGravity, true);
- if (saveGravity != Gravity.NO_GRAVITY) {
- mPreviousGravity = saveGravity;
- }
+ mTvPipBoundsAlgorithm.updateGravityOnExpansionToggled(/* expanding= */ true);
mTvPipBoundsState.setTvPipExpanded(true);
updatePinnedStackBounds();
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuController.java
index 00e4f47..8895fca 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuController.java
@@ -199,7 +199,7 @@
"%s: showMovementMenuOnly()", TAG);
setInMoveMode(true);
if (mMenuIsOpen) {
- mPipMenuView.showMoveMenu(mDelegate.getPipGravity());
+ mPipMenuView.showMoveMenu(mTvPipBoundsState.getTvPipGravity());
} else {
mCloseAfterExitMoveMenu = true;
showMenuInternal();
@@ -222,7 +222,7 @@
mMenuIsOpen = true;
grantPipMenuFocus(true);
if (mInMoveMode) {
- mPipMenuView.showMoveMenu(mDelegate.getPipGravity());
+ mPipMenuView.showMoveMenu(mTvPipBoundsState.getTvPipGravity());
} else {
mPipMenuView.showButtonsMenu(/* exitingMoveMode= */ false);
}
@@ -236,7 +236,9 @@
}
void updateGravity(int gravity) {
- mPipMenuView.showMovementHints(gravity);
+ if (mInMoveMode) {
+ mPipMenuView.showMovementHints(gravity);
+ }
}
private Rect calculateMenuSurfaceBounds(Rect pipBounds) {
@@ -500,8 +502,6 @@
void onInMoveModeChanged();
- int getPipGravity();
-
void onMenuClosed();
void closeEduText();
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
index 717ae91..8ddc3c04 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
@@ -204,10 +204,6 @@
private boolean mIsDropEntering;
private boolean mIsExiting;
- /** The target stage to dismiss to when unlock after folded. */
- @StageType
- private int mTopStageAfterFoldDismiss = STAGE_TYPE_UNDEFINED;
-
private DefaultMixedHandler mMixedHandler;
private final Toast mSplitUnsupportedToast;
@@ -976,20 +972,6 @@
return;
}
- if (!mKeyguardShowing && mTopStageAfterFoldDismiss != STAGE_TYPE_UNDEFINED) {
- if (ENABLE_SHELL_TRANSITIONS) {
- final WindowContainerTransaction wct = new WindowContainerTransaction();
- prepareExitSplitScreen(mTopStageAfterFoldDismiss, wct);
- mSplitTransitions.startDismissTransition(wct, this,
- mTopStageAfterFoldDismiss, EXIT_REASON_DEVICE_FOLDED);
- } else {
- exitSplitScreen(
- mTopStageAfterFoldDismiss == STAGE_TYPE_MAIN ? mMainStage : mSideStage,
- EXIT_REASON_DEVICE_FOLDED);
- }
- return;
- }
-
setDividerVisibility(!mKeyguardShowing, null);
}
@@ -1828,14 +1810,28 @@
sendOnBoundsChanged();
}
- private void onFoldedStateChanged(boolean folded) {
- mTopStageAfterFoldDismiss = STAGE_TYPE_UNDEFINED;
+ @VisibleForTesting
+ void onFoldedStateChanged(boolean folded) {
+ int topStageAfterFoldDismiss = STAGE_TYPE_UNDEFINED;
if (!folded) return;
+ if (!mMainStage.isActive()) return;
+
if (mMainStage.isFocused()) {
- mTopStageAfterFoldDismiss = STAGE_TYPE_MAIN;
+ topStageAfterFoldDismiss = STAGE_TYPE_MAIN;
} else if (mSideStage.isFocused()) {
- mTopStageAfterFoldDismiss = STAGE_TYPE_SIDE;
+ topStageAfterFoldDismiss = STAGE_TYPE_SIDE;
+ }
+
+ if (ENABLE_SHELL_TRANSITIONS) {
+ final WindowContainerTransaction wct = new WindowContainerTransaction();
+ prepareExitSplitScreen(topStageAfterFoldDismiss, wct);
+ mSplitTransitions.startDismissTransition(wct, this,
+ topStageAfterFoldDismiss, EXIT_REASON_DEVICE_FOLDED);
+ } else {
+ exitSplitScreen(
+ topStageAfterFoldDismiss == STAGE_TYPE_MAIN ? mMainStage : mSideStage,
+ EXIT_REASON_DEVICE_FOLDED);
}
}
@@ -2118,7 +2114,6 @@
// Update divider state after animation so that it is still around and positioned
// properly for the animation itself.
mSplitLayout.release();
- mTopStageAfterFoldDismiss = STAGE_TYPE_UNDEFINED;
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeInputListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeInputListener.java
index d3f1332..bb67145 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeInputListener.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeInputListener.java
@@ -105,6 +105,7 @@
FLAG_NOT_FOCUSABLE,
PRIVATE_FLAG_TRUSTED_OVERLAY,
TYPE_APPLICATION,
+ null /* windowToken */,
mFocusGrantToken,
TAG + " of " + decorationSurface.toString(),
mInputChannel);
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/tv/TvPipGravityTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/tv/TvPipGravityTest.java
new file mode 100644
index 0000000..6cf6e82
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/tv/TvPipGravityTest.java
@@ -0,0 +1,324 @@
+/*
+ * Copyright (C) 2022 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.wm.shell.pip.tv;
+
+import static android.view.KeyEvent.KEYCODE_DPAD_DOWN;
+import static android.view.KeyEvent.KEYCODE_DPAD_LEFT;
+import static android.view.KeyEvent.KEYCODE_DPAD_RIGHT;
+import static android.view.KeyEvent.KEYCODE_DPAD_UP;
+
+import static org.junit.Assert.assertEquals;
+
+import android.view.Gravity;
+
+import com.android.wm.shell.ShellTestCase;
+import com.android.wm.shell.pip.PipSnapAlgorithm;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.util.Locale;
+
+public class TvPipGravityTest extends ShellTestCase {
+
+ private static final float VERTICAL_EXPANDED_ASPECT_RATIO = 1f / 3;
+ private static final float HORIZONTAL_EXPANDED_ASPECT_RATIO = 3f;
+
+ @Mock
+ private PipSnapAlgorithm mMockPipSnapAlgorithm;
+
+ private TvPipBoundsState mTvPipBoundsState;
+ private TvPipBoundsAlgorithm mTvPipBoundsAlgorithm;
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+ mTvPipBoundsState = new TvPipBoundsState(mContext);
+ mTvPipBoundsAlgorithm = new TvPipBoundsAlgorithm(mContext, mTvPipBoundsState,
+ mMockPipSnapAlgorithm);
+
+ setRTL(false);
+ }
+
+ private void checkGravity(int gravityActual, int gravityExpected) {
+ assertEquals(gravityExpected, gravityActual);
+ }
+
+ private void setRTL(boolean isRtl) {
+ mContext.getResources().getConfiguration().setLayoutDirection(
+ isRtl ? new Locale("ar") : Locale.ENGLISH);
+ mTvPipBoundsState.onConfigurationChanged();
+ mTvPipBoundsAlgorithm.onConfigurationChanged(mContext);
+ }
+
+ private void assertGravityAfterExpansion(int gravityFrom, int gravityTo) {
+ mTvPipBoundsState.setTvPipExpanded(false);
+ mTvPipBoundsState.setTvPipGravity(gravityFrom);
+ mTvPipBoundsAlgorithm.updateGravityOnExpansionToggled(true);
+ checkGravity(mTvPipBoundsState.getTvPipGravity(), gravityTo);
+ }
+
+ private void assertGravityAfterCollapse(int gravityFrom, int gravityTo) {
+ mTvPipBoundsState.setTvPipExpanded(true);
+ mTvPipBoundsState.setTvPipGravity(gravityFrom);
+ mTvPipBoundsAlgorithm.updateGravityOnExpansionToggled(false);
+ checkGravity(mTvPipBoundsState.getTvPipGravity(), gravityTo);
+ }
+
+ private void assertGravityAfterExpandAndCollapse(int gravityStartAndEnd) {
+ mTvPipBoundsState.setTvPipGravity(gravityStartAndEnd);
+ mTvPipBoundsAlgorithm.updateGravityOnExpansionToggled(true);
+ mTvPipBoundsAlgorithm.updateGravityOnExpansionToggled(false);
+ checkGravity(mTvPipBoundsState.getTvPipGravity(), gravityStartAndEnd);
+ }
+
+ @Test
+ public void regularPip_defaultGravity() {
+ checkGravity(mTvPipBoundsState.getDefaultGravity(), Gravity.RIGHT | Gravity.BOTTOM);
+ }
+
+ @Test
+ public void regularPip_defaultGravity_RTL() {
+ setRTL(true);
+ checkGravity(mTvPipBoundsState.getDefaultGravity(), Gravity.LEFT | Gravity.BOTTOM);
+ }
+
+ @Test
+ public void updateGravity_expand_vertical() {
+ // Vertical expanded PiP.
+ mTvPipBoundsState.setDesiredTvExpandedAspectRatio(VERTICAL_EXPANDED_ASPECT_RATIO, true);
+
+ assertGravityAfterExpansion(Gravity.BOTTOM | Gravity.RIGHT,
+ Gravity.CENTER_VERTICAL | Gravity.RIGHT);
+ assertGravityAfterExpansion(Gravity.TOP | Gravity.RIGHT,
+ Gravity.CENTER_VERTICAL | Gravity.RIGHT);
+ assertGravityAfterExpansion(Gravity.BOTTOM | Gravity.LEFT,
+ Gravity.CENTER_VERTICAL | Gravity.LEFT);
+ assertGravityAfterExpansion(Gravity.TOP | Gravity.LEFT,
+ Gravity.CENTER_VERTICAL | Gravity.LEFT);
+ }
+
+ @Test
+ public void updateGravity_expand_horizontal() {
+ // Horizontal expanded PiP.
+ mTvPipBoundsState.setDesiredTvExpandedAspectRatio(HORIZONTAL_EXPANDED_ASPECT_RATIO, true);
+
+ assertGravityAfterExpansion(Gravity.BOTTOM | Gravity.RIGHT,
+ Gravity.BOTTOM | Gravity.CENTER_HORIZONTAL);
+ assertGravityAfterExpansion(Gravity.TOP | Gravity.RIGHT,
+ Gravity.TOP | Gravity.CENTER_HORIZONTAL);
+ assertGravityAfterExpansion(Gravity.BOTTOM | Gravity.LEFT,
+ Gravity.BOTTOM | Gravity.CENTER_HORIZONTAL);
+ assertGravityAfterExpansion(Gravity.TOP | Gravity.LEFT,
+ Gravity.TOP | Gravity.CENTER_HORIZONTAL);
+ }
+
+ @Test
+ public void updateGravity_collapse() {
+ // Vertical expansion
+ mTvPipBoundsState.setDesiredTvExpandedAspectRatio(VERTICAL_EXPANDED_ASPECT_RATIO, true);
+ assertGravityAfterCollapse(Gravity.CENTER_VERTICAL | Gravity.RIGHT,
+ Gravity.BOTTOM | Gravity.RIGHT);
+ assertGravityAfterCollapse(Gravity.CENTER_VERTICAL | Gravity.LEFT,
+ Gravity.BOTTOM | Gravity.LEFT);
+
+ // Horizontal expansion
+ mTvPipBoundsState.setDesiredTvExpandedAspectRatio(HORIZONTAL_EXPANDED_ASPECT_RATIO, true);
+ assertGravityAfterCollapse(Gravity.TOP | Gravity.CENTER_HORIZONTAL,
+ Gravity.TOP | Gravity.RIGHT);
+ assertGravityAfterCollapse(Gravity.BOTTOM | Gravity.CENTER_HORIZONTAL,
+ Gravity.BOTTOM | Gravity.RIGHT);
+ }
+
+ @Test
+ public void updateGravity_collapse_RTL() {
+ setRTL(true);
+
+ // Horizontal expansion
+ mTvPipBoundsState.setDesiredTvExpandedAspectRatio(HORIZONTAL_EXPANDED_ASPECT_RATIO, true);
+ assertGravityAfterCollapse(Gravity.TOP | Gravity.CENTER_HORIZONTAL,
+ Gravity.TOP | Gravity.LEFT);
+ assertGravityAfterCollapse(Gravity.BOTTOM | Gravity.CENTER_HORIZONTAL,
+ Gravity.BOTTOM | Gravity.LEFT);
+ }
+
+ @Test
+ public void updateGravity_expand_collapse() {
+ // Vertical expanded PiP.
+ mTvPipBoundsState.setDesiredTvExpandedAspectRatio(VERTICAL_EXPANDED_ASPECT_RATIO, true);
+
+ assertGravityAfterExpandAndCollapse(Gravity.BOTTOM | Gravity.RIGHT);
+ assertGravityAfterExpandAndCollapse(Gravity.BOTTOM | Gravity.LEFT);
+ assertGravityAfterExpandAndCollapse(Gravity.TOP | Gravity.LEFT);
+ assertGravityAfterExpandAndCollapse(Gravity.TOP | Gravity.RIGHT);
+
+ // Horizontal expanded PiP.
+ mTvPipBoundsState.setDesiredTvExpandedAspectRatio(HORIZONTAL_EXPANDED_ASPECT_RATIO, true);
+
+ assertGravityAfterExpandAndCollapse(Gravity.BOTTOM | Gravity.RIGHT);
+ assertGravityAfterExpandAndCollapse(Gravity.BOTTOM | Gravity.LEFT);
+ assertGravityAfterExpandAndCollapse(Gravity.TOP | Gravity.LEFT);
+ assertGravityAfterExpandAndCollapse(Gravity.TOP | Gravity.RIGHT);
+ }
+
+ @Test
+ public void updateGravity_expand_move_collapse() {
+ // Vertical expanded PiP.
+ mTvPipBoundsState.setDesiredTvExpandedAspectRatio(VERTICAL_EXPANDED_ASPECT_RATIO, true);
+ expandMoveCollapseCheck(Gravity.TOP | Gravity.RIGHT, KEYCODE_DPAD_LEFT,
+ Gravity.TOP | Gravity.LEFT);
+
+ // Horizontal expanded PiP.
+ mTvPipBoundsState.setDesiredTvExpandedAspectRatio(HORIZONTAL_EXPANDED_ASPECT_RATIO, true);
+ expandMoveCollapseCheck(Gravity.BOTTOM | Gravity.LEFT, KEYCODE_DPAD_UP,
+ Gravity.TOP | Gravity.LEFT);
+ }
+
+ private void expandMoveCollapseCheck(int gravityFrom, int keycode, int gravityTo) {
+ // Expand
+ mTvPipBoundsState.setTvPipExpanded(false);
+ mTvPipBoundsState.setTvPipGravity(gravityFrom);
+ mTvPipBoundsAlgorithm.updateGravityOnExpansionToggled(true);
+ // Move
+ mTvPipBoundsAlgorithm.updateGravity(keycode);
+ // Collapse
+ mTvPipBoundsState.setTvPipExpanded(true);
+ mTvPipBoundsAlgorithm.updateGravityOnExpansionToggled(false);
+
+ checkGravity(mTvPipBoundsState.getTvPipGravity(), gravityTo);
+ }
+
+ private void moveAndCheckGravity(int keycode, int gravityEnd, boolean expectChange) {
+ assertEquals(expectChange, mTvPipBoundsAlgorithm.updateGravity(keycode));
+ checkGravity(mTvPipBoundsState.getTvPipGravity(), gravityEnd);
+ }
+
+ @Test
+ public void updateGravity_move_regular_valid() {
+ mTvPipBoundsState.setTvPipGravity(Gravity.BOTTOM | Gravity.RIGHT);
+ // clockwise
+ moveAndCheckGravity(KEYCODE_DPAD_LEFT, Gravity.BOTTOM | Gravity.LEFT, true);
+ moveAndCheckGravity(KEYCODE_DPAD_UP, Gravity.TOP | Gravity.LEFT, true);
+ moveAndCheckGravity(KEYCODE_DPAD_RIGHT, Gravity.TOP | Gravity.RIGHT, true);
+ moveAndCheckGravity(KEYCODE_DPAD_DOWN, Gravity.BOTTOM | Gravity.RIGHT, true);
+ // anti-clockwise
+ moveAndCheckGravity(KEYCODE_DPAD_UP, Gravity.TOP | Gravity.RIGHT, true);
+ moveAndCheckGravity(KEYCODE_DPAD_LEFT, Gravity.TOP | Gravity.LEFT, true);
+ moveAndCheckGravity(KEYCODE_DPAD_DOWN, Gravity.BOTTOM | Gravity.LEFT, true);
+ moveAndCheckGravity(KEYCODE_DPAD_RIGHT, Gravity.BOTTOM | Gravity.RIGHT, true);
+ }
+
+ @Test
+ public void updateGravity_move_expanded_valid() {
+ mTvPipBoundsState.setTvPipExpanded(true);
+
+ // Vertical expanded PiP.
+ mTvPipBoundsState.setDesiredTvExpandedAspectRatio(VERTICAL_EXPANDED_ASPECT_RATIO, true);
+ mTvPipBoundsState.setTvPipGravity(Gravity.CENTER_VERTICAL | Gravity.RIGHT);
+ moveAndCheckGravity(KEYCODE_DPAD_LEFT, Gravity.CENTER_VERTICAL | Gravity.LEFT, true);
+ moveAndCheckGravity(KEYCODE_DPAD_RIGHT, Gravity.CENTER_VERTICAL | Gravity.RIGHT, true);
+
+ // Horizontal expanded PiP.
+ mTvPipBoundsState.setDesiredTvExpandedAspectRatio(HORIZONTAL_EXPANDED_ASPECT_RATIO, true);
+ mTvPipBoundsState.setTvPipGravity(Gravity.BOTTOM | Gravity.CENTER_HORIZONTAL);
+ moveAndCheckGravity(KEYCODE_DPAD_UP, Gravity.TOP | Gravity.CENTER_HORIZONTAL, true);
+ moveAndCheckGravity(KEYCODE_DPAD_DOWN, Gravity.BOTTOM | Gravity.CENTER_HORIZONTAL, true);
+ }
+
+ @Test
+ public void updateGravity_move_regular_invalid() {
+ int gravity = Gravity.BOTTOM | Gravity.RIGHT;
+ mTvPipBoundsState.setTvPipGravity(gravity);
+ moveAndCheckGravity(KEYCODE_DPAD_DOWN, gravity, false);
+ moveAndCheckGravity(KEYCODE_DPAD_RIGHT, gravity, false);
+
+ gravity = Gravity.BOTTOM | Gravity.LEFT;
+ mTvPipBoundsState.setTvPipGravity(gravity);
+ moveAndCheckGravity(KEYCODE_DPAD_DOWN, gravity, false);
+ moveAndCheckGravity(KEYCODE_DPAD_LEFT, gravity, false);
+
+ gravity = Gravity.TOP | Gravity.LEFT;
+ mTvPipBoundsState.setTvPipGravity(gravity);
+ moveAndCheckGravity(KEYCODE_DPAD_UP, gravity, false);
+ moveAndCheckGravity(KEYCODE_DPAD_LEFT, gravity, false);
+
+ gravity = Gravity.TOP | Gravity.RIGHT;
+ mTvPipBoundsState.setTvPipGravity(gravity);
+ moveAndCheckGravity(KEYCODE_DPAD_UP, gravity, false);
+ moveAndCheckGravity(KEYCODE_DPAD_RIGHT, gravity, false);
+ }
+
+ @Test
+ public void updateGravity_move_expanded_invalid() {
+ mTvPipBoundsState.setTvPipExpanded(true);
+
+ // Vertical expanded PiP.
+ mTvPipBoundsState.setDesiredTvExpandedAspectRatio(VERTICAL_EXPANDED_ASPECT_RATIO, true);
+ mTvPipBoundsState.setTvPipGravity(Gravity.CENTER_VERTICAL | Gravity.RIGHT);
+ moveAndCheckGravity(KEYCODE_DPAD_RIGHT, Gravity.CENTER_VERTICAL | Gravity.RIGHT, false);
+ moveAndCheckGravity(KEYCODE_DPAD_UP, Gravity.CENTER_VERTICAL | Gravity.RIGHT, false);
+ moveAndCheckGravity(KEYCODE_DPAD_DOWN, Gravity.CENTER_VERTICAL | Gravity.RIGHT, false);
+
+ mTvPipBoundsState.setTvPipGravity(Gravity.CENTER_VERTICAL | Gravity.LEFT);
+ moveAndCheckGravity(KEYCODE_DPAD_LEFT, Gravity.CENTER_VERTICAL | Gravity.LEFT, false);
+ moveAndCheckGravity(KEYCODE_DPAD_UP, Gravity.CENTER_VERTICAL | Gravity.LEFT, false);
+ moveAndCheckGravity(KEYCODE_DPAD_DOWN, Gravity.CENTER_VERTICAL | Gravity.LEFT, false);
+
+ // Horizontal expanded PiP.
+ mTvPipBoundsState.setDesiredTvExpandedAspectRatio(HORIZONTAL_EXPANDED_ASPECT_RATIO, true);
+ mTvPipBoundsState.setTvPipGravity(Gravity.BOTTOM | Gravity.CENTER_HORIZONTAL);
+ moveAndCheckGravity(KEYCODE_DPAD_DOWN, Gravity.BOTTOM | Gravity.CENTER_HORIZONTAL, false);
+ moveAndCheckGravity(KEYCODE_DPAD_LEFT, Gravity.BOTTOM | Gravity.CENTER_HORIZONTAL, false);
+ moveAndCheckGravity(KEYCODE_DPAD_RIGHT, Gravity.BOTTOM | Gravity.CENTER_HORIZONTAL, false);
+
+ mTvPipBoundsState.setTvPipGravity(Gravity.TOP | Gravity.CENTER_HORIZONTAL);
+ moveAndCheckGravity(KEYCODE_DPAD_UP, Gravity.TOP | Gravity.CENTER_HORIZONTAL, false);
+ moveAndCheckGravity(KEYCODE_DPAD_LEFT, Gravity.TOP | Gravity.CENTER_HORIZONTAL, false);
+ moveAndCheckGravity(KEYCODE_DPAD_RIGHT, Gravity.TOP | Gravity.CENTER_HORIZONTAL, false);
+ }
+
+ @Test
+ public void previousCollapsedGravity_defaultValue() {
+ assertEquals(mTvPipBoundsState.getTvPipPreviousCollapsedGravity(),
+ mTvPipBoundsState.getDefaultGravity());
+ setRTL(true);
+ assertEquals(mTvPipBoundsState.getTvPipPreviousCollapsedGravity(),
+ mTvPipBoundsState.getDefaultGravity());
+ }
+
+ @Test
+ public void previousCollapsedGravity_changes_on_RTL() {
+ mTvPipBoundsState.setTvPipPreviousCollapsedGravity(Gravity.TOP | Gravity.LEFT);
+ setRTL(true);
+ assertEquals(mTvPipBoundsState.getTvPipPreviousCollapsedGravity(),
+ Gravity.TOP | Gravity.RIGHT);
+ setRTL(false);
+ assertEquals(mTvPipBoundsState.getTvPipPreviousCollapsedGravity(),
+ Gravity.TOP | Gravity.LEFT);
+
+ mTvPipBoundsState.setTvPipPreviousCollapsedGravity(Gravity.BOTTOM | Gravity.RIGHT);
+ setRTL(true);
+ assertEquals(mTvPipBoundsState.getTvPipPreviousCollapsedGravity(),
+ Gravity.BOTTOM | Gravity.LEFT);
+ setRTL(false);
+ assertEquals(mTvPipBoundsState.getTvPipPreviousCollapsedGravity(),
+ Gravity.BOTTOM | Gravity.RIGHT);
+ }
+
+}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageCoordinatorTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageCoordinatorTests.java
index 3569860..65e1ea8 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageCoordinatorTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageCoordinatorTests.java
@@ -155,7 +155,7 @@
final ActivityManager.RunningTaskInfo task = new TestRunningTaskInfoBuilder().build();
// Verify move to undefined stage while split screen not activated moves task to side stage.
- when(mMainStage.isActive()).thenReturn(false);
+ when(mStageCoordinator.isSplitScreenVisible()).thenReturn(false);
mStageCoordinator.setSideStagePosition(SPLIT_POSITION_TOP_OR_LEFT, null);
mStageCoordinator.moveToStage(task, STAGE_TYPE_UNDEFINED, SPLIT_POSITION_BOTTOM_OR_RIGHT,
new WindowContainerTransaction());
@@ -163,7 +163,7 @@
assertEquals(SPLIT_POSITION_BOTTOM_OR_RIGHT, mStageCoordinator.getSideStagePosition());
// Verify move to undefined stage after split screen activated moves task based on position.
- when(mMainStage.isActive()).thenReturn(true);
+ when(mStageCoordinator.isSplitScreenVisible()).thenReturn(true);
assertEquals(SPLIT_POSITION_TOP_OR_LEFT, mStageCoordinator.getMainStagePosition());
mStageCoordinator.moveToStage(task, STAGE_TYPE_UNDEFINED, SPLIT_POSITION_TOP_OR_LEFT,
new WindowContainerTransaction());
@@ -262,7 +262,7 @@
@Test
public void testResolveStartStage_afterSplitActivated_retrievesStagePosition() {
- when(mMainStage.isActive()).thenReturn(true);
+ when(mStageCoordinator.isSplitScreenVisible()).thenReturn(true);
mStageCoordinator.setSideStagePosition(SPLIT_POSITION_TOP_OR_LEFT, null /* wct */);
mStageCoordinator.resolveStartStage(STAGE_TYPE_UNDEFINED, SPLIT_POSITION_TOP_OR_LEFT,
@@ -320,4 +320,16 @@
assertTrue(options.getBoolean(
KEY_PENDING_INTENT_BACKGROUND_ACTIVITY_ALLOWED_BY_PERMISSION));
}
+
+ @Test
+ public void testExitSplitScreenAfterFolded() {
+ when(mMainStage.isActive()).thenReturn(true);
+ when(mMainStage.isFocused()).thenReturn(true);
+ when(mMainStage.getTopVisibleChildTaskId()).thenReturn(INVALID_TASK_ID);
+
+ mStageCoordinator.onFoldedStateChanged(true);
+
+ verify(mStageCoordinator).onSplitScreenExit();
+ verify(mMainStage).deactivate(any(WindowContainerTransaction.class), eq(false));
+ }
}
diff --git a/libs/androidfw/ResourceTypes.cpp b/libs/androidfw/ResourceTypes.cpp
index 31516dc..1fed206 100644
--- a/libs/androidfw/ResourceTypes.cpp
+++ b/libs/androidfw/ResourceTypes.cpp
@@ -6890,7 +6890,8 @@
const uint32_t typeSize = dtohl(type->header.size);
const size_t newEntryCount = dtohl(type->entryCount);
-
+ const size_t entrySize = type->flags & ResTable_type::FLAG_OFFSET16 ?
+ sizeof(uint16_t) : sizeof(uint32_t);
if (kDebugLoadTableNoisy) {
printf("Type off %p: type=0x%x, headerSize=0x%x, size=%u\n",
(void*)(base-(const uint8_t*)chunk),
@@ -6898,9 +6899,9 @@
dtohs(type->header.headerSize),
typeSize);
}
- if (dtohs(type->header.headerSize)+(sizeof(uint32_t)*newEntryCount) > typeSize) {
+ if (dtohs(type->header.headerSize)+(entrySize*newEntryCount) > typeSize) {
ALOGW("ResTable_type entry index to %p extends beyond chunk end 0x%x.",
- (void*)(dtohs(type->header.headerSize) + (sizeof(uint32_t)*newEntryCount)),
+ (void*)(dtohs(type->header.headerSize) + (entrySize*newEntryCount)),
typeSize);
return (mError=BAD_TYPE);
}
diff --git a/libs/hwui/jni/FontFamily.cpp b/libs/hwui/jni/FontFamily.cpp
index c146ada..28e71d7 100644
--- a/libs/hwui/jni/FontFamily.cpp
+++ b/libs/hwui/jni/FontFamily.cpp
@@ -85,9 +85,9 @@
if (builder->fonts.empty()) {
return 0;
}
- std::shared_ptr<minikin::FontFamily> family =
- minikin::FontFamily::create(builder->langId, builder->variant,
- std::move(builder->fonts), true /* isCustomFallback */);
+ std::shared_ptr<minikin::FontFamily> family = minikin::FontFamily::create(
+ builder->langId, builder->variant, std::move(builder->fonts),
+ true /* isCustomFallback */, false /* isDefaultFallback */);
if (family->getCoverage().length() == 0) {
return 0;
}
diff --git a/libs/hwui/jni/fonts/FontFamily.cpp b/libs/hwui/jni/fonts/FontFamily.cpp
index fbfc07e..897c4d7 100644
--- a/libs/hwui/jni/fonts/FontFamily.cpp
+++ b/libs/hwui/jni/fonts/FontFamily.cpp
@@ -57,7 +57,8 @@
// Regular JNI
static jlong FontFamily_Builder_build(JNIEnv* env, jobject clazz, jlong builderPtr,
- jstring langTags, jint variant, jboolean isCustomFallback) {
+ jstring langTags, jint variant, jboolean isCustomFallback,
+ jboolean isDefaultFallback) {
std::unique_ptr<NativeFamilyBuilder> builder(toBuilder(builderPtr));
uint32_t localeId;
if (langTags == nullptr) {
@@ -66,9 +67,9 @@
ScopedUtfChars str(env, langTags);
localeId = minikin::registerLocaleList(str.c_str());
}
- std::shared_ptr<minikin::FontFamily> family =
- minikin::FontFamily::create(localeId, static_cast<minikin::FamilyVariant>(variant),
- std::move(builder->fonts), isCustomFallback);
+ std::shared_ptr<minikin::FontFamily> family = minikin::FontFamily::create(
+ localeId, static_cast<minikin::FamilyVariant>(variant), std::move(builder->fonts),
+ isCustomFallback, isDefaultFallback);
if (family->getCoverage().length() == 0) {
// No coverage means minikin rejected given font for some reasons.
jniThrowException(env, "java/lang/IllegalArgumentException",
@@ -116,10 +117,10 @@
///////////////////////////////////////////////////////////////////////////////
static const JNINativeMethod gFontFamilyBuilderMethods[] = {
- { "nInitBuilder", "()J", (void*) FontFamily_Builder_initBuilder },
- { "nAddFont", "(JJ)V", (void*) FontFamily_Builder_addFont },
- { "nBuild", "(JLjava/lang/String;IZ)J", (void*) FontFamily_Builder_build },
- { "nGetReleaseNativeFamily", "()J", (void*) FontFamily_Builder_GetReleaseFunc },
+ {"nInitBuilder", "()J", (void*)FontFamily_Builder_initBuilder},
+ {"nAddFont", "(JJ)V", (void*)FontFamily_Builder_addFont},
+ {"nBuild", "(JLjava/lang/String;IZZ)J", (void*)FontFamily_Builder_build},
+ {"nGetReleaseNativeFamily", "()J", (void*)FontFamily_Builder_GetReleaseFunc},
};
static const JNINativeMethod gFontFamilyMethods[] = {
diff --git a/location/java/android/location/GnssCapabilities.java b/location/java/android/location/GnssCapabilities.java
index a6da0a3..f9f9fa4 100644
--- a/location/java/android/location/GnssCapabilities.java
+++ b/location/java/android/location/GnssCapabilities.java
@@ -39,33 +39,35 @@
/** @hide */
public static final int TOP_HAL_CAPABILITY_SCHEDULING = 1;
/** @hide */
- public static final int TOP_HAL_CAPABILITY_MSB = 2;
+ public static final int TOP_HAL_CAPABILITY_MSB = 1 << 1;
/** @hide */
- public static final int TOP_HAL_CAPABILITY_MSA = 4;
+ public static final int TOP_HAL_CAPABILITY_MSA = 1 << 2;
/** @hide */
- public static final int TOP_HAL_CAPABILITY_SINGLE_SHOT = 8;
+ public static final int TOP_HAL_CAPABILITY_SINGLE_SHOT = 1 << 3;
/** @hide */
- public static final int TOP_HAL_CAPABILITY_ON_DEMAND_TIME = 16;
+ public static final int TOP_HAL_CAPABILITY_ON_DEMAND_TIME = 1 << 4;
/** @hide */
- public static final int TOP_HAL_CAPABILITY_GEOFENCING = 32;
+ public static final int TOP_HAL_CAPABILITY_GEOFENCING = 1 << 5;
/** @hide */
- public static final int TOP_HAL_CAPABILITY_MEASUREMENTS = 64;
+ public static final int TOP_HAL_CAPABILITY_MEASUREMENTS = 1 << 6;
/** @hide */
- public static final int TOP_HAL_CAPABILITY_NAV_MESSAGES = 128;
+ public static final int TOP_HAL_CAPABILITY_NAV_MESSAGES = 1 << 7;
/** @hide */
- public static final int TOP_HAL_CAPABILITY_LOW_POWER_MODE = 256;
+ public static final int TOP_HAL_CAPABILITY_LOW_POWER_MODE = 1 << 8;
/** @hide */
- public static final int TOP_HAL_CAPABILITY_SATELLITE_BLOCKLIST = 512;
+ public static final int TOP_HAL_CAPABILITY_SATELLITE_BLOCKLIST = 1 << 9;
/** @hide */
- public static final int TOP_HAL_CAPABILITY_MEASUREMENT_CORRECTIONS = 1024;
+ public static final int TOP_HAL_CAPABILITY_MEASUREMENT_CORRECTIONS = 1 << 10;
/** @hide */
- public static final int TOP_HAL_CAPABILITY_ANTENNA_INFO = 2048;
+ public static final int TOP_HAL_CAPABILITY_ANTENNA_INFO = 1 << 11;
/** @hide */
- public static final int TOP_HAL_CAPABILITY_CORRELATION_VECTOR = 4096;
+ public static final int TOP_HAL_CAPABILITY_CORRELATION_VECTOR = 1 << 12;
/** @hide */
- public static final int TOP_HAL_CAPABILITY_SATELLITE_PVT = 8192;
+ public static final int TOP_HAL_CAPABILITY_SATELLITE_PVT = 1 << 13;
/** @hide */
- public static final int TOP_HAL_CAPABILITY_MEASUREMENT_CORRECTIONS_FOR_DRIVING = 16384;
+ public static final int TOP_HAL_CAPABILITY_MEASUREMENT_CORRECTIONS_FOR_DRIVING = 1 << 14;
+ /** @hide */
+ public static final int TOP_HAL_CAPABILITY_ACCUMULATED_DELTA_RANGE = 1 << 15;
/** @hide */
@IntDef(flag = true, prefix = {"TOP_HAL_CAPABILITY_"}, value = {TOP_HAL_CAPABILITY_SCHEDULING,
@@ -75,7 +77,8 @@
TOP_HAL_CAPABILITY_LOW_POWER_MODE, TOP_HAL_CAPABILITY_SATELLITE_BLOCKLIST,
TOP_HAL_CAPABILITY_MEASUREMENT_CORRECTIONS, TOP_HAL_CAPABILITY_ANTENNA_INFO,
TOP_HAL_CAPABILITY_CORRELATION_VECTOR, TOP_HAL_CAPABILITY_SATELLITE_PVT,
- TOP_HAL_CAPABILITY_MEASUREMENT_CORRECTIONS_FOR_DRIVING})
+ TOP_HAL_CAPABILITY_MEASUREMENT_CORRECTIONS_FOR_DRIVING,
+ TOP_HAL_CAPABILITY_ACCUMULATED_DELTA_RANGE})
@Retention(RetentionPolicy.SOURCE)
public @interface TopHalCapabilityFlags {}
@@ -362,6 +365,19 @@
}
/**
+ * Returns {@code true} if GNSS chipset supports accumulated delta range, {@code false}
+ * otherwise.
+ *
+ * <p>The accumulated delta range information can be queried in
+ * {@link android.location.GnssMeasurement#getAccumulatedDeltaRangeState()},
+ * {@link android.location.GnssMeasurement#getAccumulatedDeltaRangeMeters()}, and
+ * {@link android.location.GnssMeasurement#getAccumulatedDeltaRangeUncertaintyMeters()}.
+ */
+ public boolean hasAccumulatedDeltaRange() {
+ return (mTopFlags & TOP_HAL_CAPABILITY_ACCUMULATED_DELTA_RANGE) != 0;
+ }
+
+ /**
* Returns {@code true} if GNSS chipset supports line-of-sight satellite identification
* measurement corrections, {@code false} otherwise.
*/
@@ -554,6 +570,9 @@
if (hasMeasurementCorrectionsForDriving()) {
builder.append("MEASUREMENT_CORRECTIONS_FOR_DRIVING ");
}
+ if (hasAccumulatedDeltaRange()) {
+ builder.append("ACCUMULATED_DELTA_RANGE ");
+ }
if (hasMeasurementCorrectionsLosSats()) {
builder.append("LOS_SATS ");
}
@@ -742,6 +761,15 @@
}
/**
+ * Sets accumulated delta range capability.
+ */
+ public @NonNull Builder setHasAccumulatedDeltaRange(boolean capable) {
+ mTopFlags = setFlag(mTopFlags, TOP_HAL_CAPABILITY_ACCUMULATED_DELTA_RANGE,
+ capable);
+ return this;
+ }
+
+ /**
* Sets measurement corrections line-of-sight satellites capability.
*/
public @NonNull Builder setHasMeasurementCorrectionsLosSats(boolean capable) {
diff --git a/media/java/android/media/AudioRecord.java b/media/java/android/media/AudioRecord.java
index 8be1bcd..6e3829a 100644
--- a/media/java/android/media/AudioRecord.java
+++ b/media/java/android/media/AudioRecord.java
@@ -16,6 +16,11 @@
package android.media;
+import static android.companion.virtual.VirtualDeviceManager.DEVICE_ID_DEFAULT;
+import static android.companion.virtual.VirtualDeviceParams.DEVICE_POLICY_DEFAULT;
+import static android.companion.virtual.VirtualDeviceParams.POLICY_TYPE_AUDIO;
+import static android.media.AudioManager.AUDIO_SESSION_ID_GENERATE;
+
import android.annotation.CallbackExecutor;
import android.annotation.FloatRange;
import android.annotation.IntDef;
@@ -26,6 +31,7 @@
import android.annotation.SystemApi;
import android.annotation.TestApi;
import android.app.ActivityThread;
+import android.companion.virtual.VirtualDeviceManager;
import android.compat.annotation.UnsupportedAppUsage;
import android.content.AttributionSource;
import android.content.AttributionSource.ScopedParcelState;
@@ -455,7 +461,7 @@
int[] sampleRate = new int[] {mSampleRate};
int[] session = new int[1];
- session[0] = sessionId;
+ session[0] = resolveSessionId(context, sessionId);
//TODO: update native initialization when information about hardware init failure
// due to capture device already open is available.
@@ -624,15 +630,15 @@
/**
* Sets the context the record belongs to. This context will be used to pull information,
- * such as {@link android.content.AttributionSource}, which will be associated with
- * the AudioRecord. However, the context itself will not be retained by the AudioRecord.
+ * such as {@link android.content.AttributionSource} and device specific session ids,
+ * which will be associated with the {@link AudioRecord} the AudioRecord.
+ * However, the context itself will not be retained by the AudioRecord.
* @param context a non-null {@link Context} instance
* @return the same Builder instance.
*/
public @NonNull Builder setContext(@NonNull Context context) {
- Objects.requireNonNull(context);
// keep reference, we only copy the data when building
- mContext = context;
+ mContext = Objects.requireNonNull(context);
return this;
}
@@ -746,6 +752,9 @@
/**
* @hide
* To be only used by system components.
+ *
+ * Note, that if there's a device specific session id asociated with the context, explicitly
+ * setting a session id using this method will override it.
* @param sessionId ID of audio session the AudioRecord must be attached to, or
* {@link AudioManager#AUDIO_SESSION_ID_GENERATE} if the session isn't known at
* construction time.
@@ -983,6 +992,45 @@
}
}
+ /**
+ * Helper method to resolve session id to be used for AudioRecord initialization.
+ *
+ * This method will assign session id in following way:
+ * 1. Explicitly requested session id has the highest priority, if there is one,
+ * it will be used.
+ * 2. If there's device-specific session id asociated with the provided context,
+ * it will be used.
+ * 3. Otherwise {@link AUDIO_SESSION_ID_GENERATE} is returned.
+ *
+ * @param context {@link Context} to use for extraction of device specific session id.
+ * @param requestedSessionId explicitly requested session id or AUDIO_SESSION_ID_GENERATE.
+ * @return session id to be passed to AudioService for the {@link AudioRecord} instance given
+ * provided {@link Context} instance and explicitly requested session id.
+ */
+ private static int resolveSessionId(@Nullable Context context, int requestedSessionId) {
+ if (requestedSessionId != AUDIO_SESSION_ID_GENERATE) {
+ // Use explicitly requested session id.
+ return requestedSessionId;
+ }
+
+ if (context == null) {
+ return AUDIO_SESSION_ID_GENERATE;
+ }
+
+ int deviceId = context.getDeviceId();
+ if (deviceId == DEVICE_ID_DEFAULT) {
+ return AUDIO_SESSION_ID_GENERATE;
+ }
+
+ VirtualDeviceManager vdm = context.getSystemService(VirtualDeviceManager.class);
+ if (vdm == null || vdm.getDevicePolicy(deviceId, POLICY_TYPE_AUDIO)
+ == DEVICE_POLICY_DEFAULT) {
+ return AUDIO_SESSION_ID_GENERATE;
+ }
+
+ return vdm.getAudioRecordingSessionId(deviceId);
+ }
+
// Convenience method for the constructor's parameter checks.
// This, getChannelMaskFromLegacyConfig and audioBuffSizeCheck are where constructor
// IllegalArgumentException-s are thrown
diff --git a/media/java/android/media/AudioTrack.java b/media/java/android/media/AudioTrack.java
index c2c752e..203429e 100644
--- a/media/java/android/media/AudioTrack.java
+++ b/media/java/android/media/AudioTrack.java
@@ -16,6 +16,11 @@
package android.media;
+import static android.companion.virtual.VirtualDeviceManager.DEVICE_ID_DEFAULT;
+import static android.companion.virtual.VirtualDeviceParams.DEVICE_POLICY_DEFAULT;
+import static android.companion.virtual.VirtualDeviceParams.POLICY_TYPE_AUDIO;
+import static android.media.AudioManager.AUDIO_SESSION_ID_GENERATE;
+
import android.annotation.CallbackExecutor;
import android.annotation.FloatRange;
import android.annotation.IntDef;
@@ -25,7 +30,9 @@
import android.annotation.RequiresPermission;
import android.annotation.SystemApi;
import android.annotation.TestApi;
+import android.companion.virtual.VirtualDeviceManager;
import android.compat.annotation.UnsupportedAppUsage;
+import android.content.Context;
import android.media.audiopolicy.AudioMix;
import android.media.audiopolicy.AudioMixingRule;
import android.media.audiopolicy.AudioPolicy;
@@ -545,7 +552,7 @@
/**
* Audio session ID
*/
- private int mSessionId = AudioManager.AUDIO_SESSION_ID_GENERATE;
+ private int mSessionId = AUDIO_SESSION_ID_GENERATE;
/**
* HW_AV_SYNC track AV Sync Header
*/
@@ -645,7 +652,7 @@
int bufferSizeInBytes, int mode)
throws IllegalArgumentException {
this(streamType, sampleRateInHz, channelConfig, audioFormat,
- bufferSizeInBytes, mode, AudioManager.AUDIO_SESSION_ID_GENERATE);
+ bufferSizeInBytes, mode, AUDIO_SESSION_ID_GENERATE);
}
/**
@@ -749,12 +756,12 @@
public AudioTrack(AudioAttributes attributes, AudioFormat format, int bufferSizeInBytes,
int mode, int sessionId)
throws IllegalArgumentException {
- this(attributes, format, bufferSizeInBytes, mode, sessionId, false /*offload*/,
- ENCAPSULATION_MODE_NONE, null /* tunerConfiguration */);
+ this(null /* context */, attributes, format, bufferSizeInBytes, mode, sessionId,
+ false /*offload*/, ENCAPSULATION_MODE_NONE, null /* tunerConfiguration */);
}
- private AudioTrack(AudioAttributes attributes, AudioFormat format, int bufferSizeInBytes,
- int mode, int sessionId, boolean offload, int encapsulationMode,
+ private AudioTrack(@Nullable Context context, AudioAttributes attributes, AudioFormat format,
+ int bufferSizeInBytes, int mode, int sessionId, boolean offload, int encapsulationMode,
@Nullable TunerConfiguration tunerConfiguration)
throws IllegalArgumentException {
super(attributes, AudioPlaybackConfiguration.PLAYER_TYPE_JAM_AUDIOTRACK);
@@ -817,7 +824,13 @@
int[] sampleRate = new int[] {mSampleRate};
int[] session = new int[1];
- session[0] = sessionId;
+ if (sessionId == AUDIO_SESSION_ID_GENERATE) {
+ // If there's no specific session id requested, try to get one from context.
+ session[0] = getSessionIdForContext(context);
+ } else {
+ session[0] = sessionId;
+ }
+
// native initialization
int initResult = native_setup(new WeakReference<AudioTrack>(this), mAttributes,
sampleRate, mChannelMask, mChannelIndexMask, mAudioFormat,
@@ -1028,11 +1041,12 @@
* <br>Offload is false by default.
*/
public static class Builder {
+ private Context mContext;
private AudioAttributes mAttributes;
private AudioFormat mFormat;
private int mBufferSizeInBytes;
private int mEncapsulationMode = ENCAPSULATION_MODE_NONE;
- private int mSessionId = AudioManager.AUDIO_SESSION_ID_GENERATE;
+ private int mSessionId = AUDIO_SESSION_ID_GENERATE;
private int mMode = MODE_STREAM;
private int mPerformanceMode = PERFORMANCE_MODE_NONE;
private boolean mOffload = false;
@@ -1046,6 +1060,19 @@
}
/**
+ * Sets the context the track belongs to. This context will be used to pull information,
+ * such as {@link android.content.AttributionSource} and device specific audio session ids,
+ * which will be associated with the {@link AudioTrack}. However, the context itself will
+ * not be retained by the {@link AudioTrack}.
+ * @param context a non-null {@link Context} instance
+ * @return the same Builder instance.
+ */
+ public @NonNull Builder setContext(@NonNull Context context) {
+ mContext = Objects.requireNonNull(context);
+ return this;
+ }
+
+ /**
* Sets the {@link AudioAttributes}.
* @param attributes a non-null {@link AudioAttributes} instance that describes the audio
* data to be played.
@@ -1152,6 +1179,10 @@
/**
* Sets the session ID the {@link AudioTrack} will be attached to.
+ *
+ * Note, that if there's a device specific session id asociated with the context, explicitly
+ * setting a session id using this method will override it
+ * (see {@link Builder#setContext(Context)}).
* @param sessionId a strictly positive ID number retrieved from another
* <code>AudioTrack</code> via {@link AudioTrack#getAudioSessionId()} or allocated by
* {@link AudioManager} via {@link AudioManager#generateAudioSessionId()}, or
@@ -1161,7 +1192,7 @@
*/
public @NonNull Builder setSessionId(@IntRange(from = 1) int sessionId)
throws IllegalArgumentException {
- if ((sessionId != AudioManager.AUDIO_SESSION_ID_GENERATE) && (sessionId < 1)) {
+ if ((sessionId != AUDIO_SESSION_ID_GENERATE) && (sessionId < 1)) {
throw new IllegalArgumentException("Invalid audio session ID " + sessionId);
}
mSessionId = sessionId;
@@ -1371,8 +1402,8 @@
try {
final AudioTrack track = new AudioTrack(
- mAttributes, mFormat, mBufferSizeInBytes, mMode, mSessionId, mOffload,
- mEncapsulationMode, mTunerConfiguration);
+ mContext, mAttributes, mFormat, mBufferSizeInBytes, mMode, mSessionId,
+ mOffload, mEncapsulationMode, mTunerConfiguration);
if (track.getState() == STATE_UNINITIALIZED) {
// release is not necessary
throw new UnsupportedOperationException("Cannot create AudioTrack");
@@ -1385,6 +1416,32 @@
}
/**
+ * Helper method to extract device specific audio session id from Context.
+ *
+ * @param context {@link Context} to use for extraction of device specific session id.
+ * @return device specific session id. If context is null or doesn't have specific audio
+ * session id associated, this method returns {@link AUDIO_SESSION_ID_GENERATE}.
+ */
+ private static int getSessionIdForContext(@Nullable Context context) {
+ if (context == null) {
+ return AUDIO_SESSION_ID_GENERATE;
+ }
+
+ int deviceId = context.getDeviceId();
+ if (deviceId == DEVICE_ID_DEFAULT) {
+ return AUDIO_SESSION_ID_GENERATE;
+ }
+
+ VirtualDeviceManager vdm = context.getSystemService(VirtualDeviceManager.class);
+ if (vdm == null || vdm.getDevicePolicy(deviceId, POLICY_TYPE_AUDIO)
+ == DEVICE_POLICY_DEFAULT) {
+ return AUDIO_SESSION_ID_GENERATE;
+ }
+
+ return vdm.getAudioPlaybackSessionId(deviceId);
+ }
+
+ /**
* Sets an {@link AudioPolicy} to automatically unregister when the track is released.
*
* <p>This is to prevent users of the call audio injection API from having to manually
diff --git a/media/java/android/media/ImageReader.java b/media/java/android/media/ImageReader.java
index f223bfd..72aaa35 100644
--- a/media/java/android/media/ImageReader.java
+++ b/media/java/android/media/ImageReader.java
@@ -1199,6 +1199,7 @@
case ImageFormat.RAW_PRIVATE:
case ImageFormat.DEPTH_JPEG:
case ImageFormat.HEIC:
+ case ImageFormat.JPEG_R:
width = ImageReader.this.getWidth();
break;
default:
@@ -1217,6 +1218,7 @@
case ImageFormat.RAW_PRIVATE:
case ImageFormat.DEPTH_JPEG:
case ImageFormat.HEIC:
+ case ImageFormat.JPEG_R:
height = ImageReader.this.getHeight();
break;
default:
diff --git a/media/java/android/media/ImageUtils.java b/media/java/android/media/ImageUtils.java
index 2f1a36c..8f7019d 100644
--- a/media/java/android/media/ImageUtils.java
+++ b/media/java/android/media/ImageUtils.java
@@ -68,6 +68,7 @@
case ImageFormat.RAW_DEPTH10:
case ImageFormat.DEPTH_JPEG:
case ImageFormat.HEIC:
+ case ImageFormat.JPEG_R:
return 1;
case ImageFormat.PRIVATE:
return 0;
@@ -231,6 +232,7 @@
case ImageFormat.DEPTH_POINT_CLOUD:
case ImageFormat.DEPTH_JPEG:
case ImageFormat.HEIC:
+ case ImageFormat.JPEG_R:
estimatedBytePerPixel = 0.3;
break;
case ImageFormat.Y8:
@@ -304,6 +306,7 @@
case ImageFormat.RAW_DEPTH:
case ImageFormat.RAW_DEPTH10:
case ImageFormat.HEIC:
+ case ImageFormat.JPEG_R:
return new Size(image.getWidth(), image.getHeight());
case ImageFormat.PRIVATE:
return new Size(0, 0);
diff --git a/media/jni/android_media_MediaDrm.cpp b/media/jni/android_media_MediaDrm.cpp
index c9d920f..681d76a 100644
--- a/media/jni/android_media_MediaDrm.cpp
+++ b/media/jni/android_media_MediaDrm.cpp
@@ -244,6 +244,20 @@
}
return arrayList;
}
+
+int drmThrowException(JNIEnv* env, const char *className, const DrmStatus &err, const char *msg) {
+ using namespace android::jnihelp;
+ jstring _detailMessage = CreateExceptionMsg(env, msg);
+ int _status = ThrowException(env, className, "(Ljava/lang/String;III)V",
+ _detailMessage,
+ err.getCdmErr(),
+ err.getOemErr(),
+ err.getContext());
+ if (_detailMessage != NULL) {
+ env->DeleteLocalRef(_detailMessage);
+ }
+ return _status;
+}
} // namespace anonymous
// ----------------------------------------------------------------------------
@@ -393,8 +407,8 @@
jint jerr = MediaErrorToJavaError(err);
jobject exception = env->NewObject(gFields.stateException.classId,
- gFields.stateException.init, static_cast<int>(jerr),
- env->NewStringUTF(msg));
+ gFields.stateException.init, env->NewStringUTF(msg), static_cast<int>(jerr),
+ err.getCdmErr(), err.getOemErr(), err.getContext());
env->Throw(static_cast<jthrowable>(exception));
}
@@ -411,10 +425,13 @@
}
jobject exception = env->NewObject(gFields.sessionException.classId,
- gFields.sessionException.init, static_cast<int>(err),
- env->NewStringUTF(msg));
+ gFields.sessionException.init,
+ env->NewStringUTF(msg),
+ jErrorCode,
+ err.getCdmErr(),
+ err.getOemErr(),
+ err.getContext());
- env->SetIntField(exception, gFields.sessionException.errorCode, jErrorCode);
env->Throw(static_cast<jthrowable>(exception));
}
@@ -437,13 +454,13 @@
jniThrowException(env, "java/lang/UnsupportedOperationException", msg);
return true;
} else if (err == ERROR_DRM_NOT_PROVISIONED) {
- jniThrowException(env, "android/media/NotProvisionedException", msg);
+ drmThrowException(env, "android/media/NotProvisionedException", err, msg);
return true;
} else if (err == ERROR_DRM_RESOURCE_BUSY) {
- jniThrowException(env, "android/media/ResourceBusyException", msg);
+ drmThrowException(env, "android/media/ResourceBusyException", err, msg);
return true;
} else if (err == ERROR_DRM_DEVICE_REVOKED) {
- jniThrowException(env, "android/media/DeniedByServerException", msg);
+ drmThrowException(env, "android/media/DeniedByServerException", err, msg);
return true;
} else if (err == DEAD_OBJECT) {
jniThrowException(env, "android/media/MediaDrmResetException", msg);
@@ -915,11 +932,11 @@
gFields.arraylistClassId = static_cast<jclass>(env->NewGlobalRef(clazz));
FIND_CLASS(clazz, "android/media/MediaDrm$MediaDrmStateException");
- GET_METHOD_ID(gFields.stateException.init, clazz, "<init>", "(ILjava/lang/String;)V");
+ GET_METHOD_ID(gFields.stateException.init, clazz, "<init>", "(Ljava/lang/String;IIII)V");
gFields.stateException.classId = static_cast<jclass>(env->NewGlobalRef(clazz));
FIND_CLASS(clazz, "android/media/MediaDrm$SessionException");
- GET_METHOD_ID(gFields.sessionException.init, clazz, "<init>", "(ILjava/lang/String;)V");
+ GET_METHOD_ID(gFields.sessionException.init, clazz, "<init>", "(Ljava/lang/String;IIII)V");
gFields.sessionException.classId = static_cast<jclass>(env->NewGlobalRef(clazz));
GET_FIELD_ID(gFields.sessionException.errorCode, clazz, "mErrorCode", "I");
diff --git a/media/tests/MediaFrameworkTest/Android.bp b/media/tests/MediaFrameworkTest/Android.bp
index 48d56d8..06ec949e 100644
--- a/media/tests/MediaFrameworkTest/Android.bp
+++ b/media/tests/MediaFrameworkTest/Android.bp
@@ -13,13 +13,18 @@
libs: [
"android.test.runner",
"android.test.base",
+ "android.test.mock",
],
static_libs: [
- "mockito-target-minus-junit4",
+ "mockito-target-inline-minus-junit4",
"androidx.test.ext.junit",
"androidx.test.rules",
"android-ex-camera2",
- "testng"
+ "testng",
+ ],
+ jni_libs: [
+ "libdexmakerjvmtiagent",
+ "libstaticjvmtiagent",
],
platform_apis: true,
}
diff --git a/media/tests/MediaFrameworkTest/AndroidManifest.xml b/media/tests/MediaFrameworkTest/AndroidManifest.xml
index 9211a7e..41ac285 100644
--- a/media/tests/MediaFrameworkTest/AndroidManifest.xml
+++ b/media/tests/MediaFrameworkTest/AndroidManifest.xml
@@ -24,7 +24,8 @@
<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS"/>
<uses-permission android:name="android.permission.WAKE_LOCK"/>
- <application android:networkSecurityConfig="@xml/network_security_config">
+ <application android:networkSecurityConfig="@xml/network_security_config"
+ android:debuggable="true">
<uses-library android:name="android.test.runner"/>
<activity android:label="@string/app_name"
android:name="MediaFrameworkTest"
diff --git a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/AudioRecordUnitTest.java b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/AudioRecordUnitTest.java
new file mode 100644
index 0000000..9c813c2
--- /dev/null
+++ b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/AudioRecordUnitTest.java
@@ -0,0 +1,149 @@
+/*
+ * Copyright (C) 2022 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.mediaframeworktest.unit;
+
+import static android.companion.virtual.VirtualDeviceManager.DEVICE_ID_DEFAULT;
+import static android.companion.virtual.VirtualDeviceParams.DEVICE_POLICY_CUSTOM;
+import static android.companion.virtual.VirtualDeviceParams.DEVICE_POLICY_DEFAULT;
+import static android.companion.virtual.VirtualDeviceParams.POLICY_TYPE_AUDIO;
+import static android.media.AudioManager.AUDIO_SESSION_ID_GENERATE;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import android.companion.virtual.VirtualDeviceManager;
+import android.content.Context;
+import android.media.AudioFormat;
+import android.media.AudioManager;
+import android.media.AudioRecord;
+import android.media.MediaRecorder;
+import android.test.mock.MockContext;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+public class AudioRecordUnitTest {
+ private static final int TEST_SAMPLE_RATE = 44000;
+ private static final int TEST_VIRTUAL_DEVICE_ID = 42;
+ private static final AudioFormat TEST_AUDIO_FORMAT = new AudioFormat.Builder().setSampleRate(
+ TEST_SAMPLE_RATE).setEncoding(AudioFormat.ENCODING_PCM_16BIT).setChannelMask(
+ AudioFormat.CHANNEL_IN_MONO).build();
+ private static final int TEST_BUFFER_SIZE = AudioRecord.getMinBufferSize(
+ TEST_AUDIO_FORMAT.getSampleRate(),
+ TEST_AUDIO_FORMAT.getChannelMask(), TEST_AUDIO_FORMAT.getEncoding());
+
+ @Test
+ public void testBuilderConstructionWithContext_defaultDeviceExplicitSessionId() {
+ Context mockDefaultDeviceContext = getVirtualDeviceMockContext(
+ DEVICE_ID_DEFAULT, /*vdm=*/null);
+ int sessionId = getContext().getSystemService(AudioManager.class).generateAudioSessionId();
+
+ AudioRecord audioRecord = new AudioRecord.Builder().setContext(
+ mockDefaultDeviceContext).setAudioSource(
+ MediaRecorder.AudioSource.DEFAULT).setAudioFormat(
+ TEST_AUDIO_FORMAT).setBufferSizeInBytes(TEST_BUFFER_SIZE).setSessionId(
+ sessionId).build();
+
+ assertEquals(AudioRecord.STATE_INITIALIZED, audioRecord.getState());
+ assertEquals(sessionId, audioRecord.getAudioSessionId());
+ }
+
+ @Test
+ public void testBuilderConstructionWithContext_virtualDeviceDefaultAudioPolicy() {
+ int vdmPlaybackSessionId = getContext().getSystemService(
+ AudioManager.class).generateAudioSessionId();
+ VirtualDeviceManager mockVdm = getMockVirtualDeviceManager(TEST_VIRTUAL_DEVICE_ID,
+ vdmPlaybackSessionId, DEVICE_POLICY_DEFAULT);
+ Context virtualDeviceContext = getVirtualDeviceMockContext(TEST_VIRTUAL_DEVICE_ID, mockVdm);
+
+ AudioRecord audioRecord = new AudioRecord.Builder().setContext(
+ virtualDeviceContext).setAudioSource(
+ MediaRecorder.AudioSource.DEFAULT).setAudioFormat(
+ TEST_AUDIO_FORMAT).setBufferSizeInBytes(TEST_BUFFER_SIZE).build();
+
+ assertEquals(AudioRecord.STATE_INITIALIZED, audioRecord.getState());
+ assertNotEquals(vdmPlaybackSessionId, audioRecord.getAudioSessionId());
+ }
+
+ @Test
+ public void testBuilderConstructionWithContext_virtualDeviceCustomAudioPolicy() {
+ int vdmRecordingSessionId = getContext().getSystemService(
+ AudioManager.class).generateAudioSessionId();
+ VirtualDeviceManager mockVdm = getMockVirtualDeviceManager(TEST_VIRTUAL_DEVICE_ID,
+ vdmRecordingSessionId, DEVICE_POLICY_CUSTOM);
+ Context virtualDeviceContext = getVirtualDeviceMockContext(TEST_VIRTUAL_DEVICE_ID, mockVdm);
+
+ AudioRecord audioRecord = new AudioRecord.Builder().setContext(
+ virtualDeviceContext).setAudioSource(
+ MediaRecorder.AudioSource.DEFAULT).setAudioFormat(
+ TEST_AUDIO_FORMAT).setBufferSizeInBytes(TEST_BUFFER_SIZE).build();
+
+ assertEquals(AudioRecord.STATE_INITIALIZED, audioRecord.getState());
+ assertEquals(vdmRecordingSessionId, audioRecord.getAudioSessionId());
+ }
+
+ @Test
+ public void testBuilderConstructionWithContext_virtualDeviceSetSessionIdOverridesContext() {
+ int vdmRecordingSessionId = getContext().getSystemService(
+ AudioManager.class).generateAudioSessionId();
+ int anotherSessionId = getContext().getSystemService(
+ AudioManager.class).generateAudioSessionId();
+ VirtualDeviceManager mockVdm = getMockVirtualDeviceManager(TEST_VIRTUAL_DEVICE_ID,
+ vdmRecordingSessionId, DEVICE_POLICY_CUSTOM);
+ Context virtualDeviceContext = getVirtualDeviceMockContext(TEST_VIRTUAL_DEVICE_ID, mockVdm);
+
+ AudioRecord audioRecord = new AudioRecord.Builder().setContext(
+ virtualDeviceContext).setAudioSource(
+ MediaRecorder.AudioSource.DEFAULT).setAudioSource(
+ MediaRecorder.AudioSource.DEFAULT).setBufferSizeInBytes(
+ TEST_BUFFER_SIZE).setSessionId(
+ anotherSessionId).build();
+
+ assertEquals(AudioRecord.STATE_INITIALIZED, audioRecord.getState());
+ assertEquals(anotherSessionId, audioRecord.getAudioSessionId());
+ }
+
+ private Context getContext() {
+ return InstrumentationRegistry.getInstrumentation().getContext();
+ }
+
+ private Context getVirtualDeviceMockContext(int deviceId, VirtualDeviceManager vdm) {
+ MockContext mockContext = mock(MockContext.class);
+ when(mockContext.getDeviceId()).thenReturn(deviceId);
+ when(mockContext.getSystemService(VirtualDeviceManager.class)).thenReturn(vdm);
+ when(mockContext.getAttributionSource()).thenReturn(getContext().getAttributionSource());
+ return mockContext;
+ }
+
+ private static VirtualDeviceManager getMockVirtualDeviceManager(int deviceId,
+ int recordingSessionId, int audioDevicePolicy) {
+ VirtualDeviceManager vdmMock = mock(VirtualDeviceManager.class);
+ when(vdmMock.getAudioRecordingSessionId(anyInt())).thenReturn(AUDIO_SESSION_ID_GENERATE);
+ when(vdmMock.getAudioRecordingSessionId(deviceId)).thenReturn(recordingSessionId);
+ when(vdmMock.getDevicePolicy(anyInt(), anyInt())).thenReturn(DEVICE_POLICY_DEFAULT);
+ when(vdmMock.getDevicePolicy(deviceId, POLICY_TYPE_AUDIO)).thenReturn(audioDevicePolicy);
+ return vdmMock;
+ }
+
+}
diff --git a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/AudioTrackUnitTest.java b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/AudioTrackUnitTest.java
new file mode 100644
index 0000000..ffed39a
--- /dev/null
+++ b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/AudioTrackUnitTest.java
@@ -0,0 +1,145 @@
+/*
+ * Copyright (C) 2022 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.mediaframeworktest.unit;
+
+import static android.companion.virtual.VirtualDeviceManager.DEVICE_ID_DEFAULT;
+import static android.companion.virtual.VirtualDeviceParams.DEVICE_POLICY_CUSTOM;
+import static android.companion.virtual.VirtualDeviceParams.DEVICE_POLICY_DEFAULT;
+import static android.companion.virtual.VirtualDeviceParams.POLICY_TYPE_AUDIO;
+import static android.media.AudioManager.AUDIO_SESSION_ID_GENERATE;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import android.companion.virtual.VirtualDeviceManager;
+import android.content.Context;
+import android.media.AudioAttributes;
+import android.media.AudioFormat;
+import android.media.AudioManager;
+import android.media.AudioTrack;
+import android.test.mock.MockContext;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+public class AudioTrackUnitTest {
+ private static final int TEST_SAMPLE_RATE = 44000;
+ private static final int TEST_VIRTUAL_DEVICE_ID = 42;
+ private static final AudioAttributes TEST_AUDIO_ATTRIBUTES =
+ new AudioAttributes.Builder().setUsage(AudioAttributes.USAGE_MEDIA).build();
+ private static final AudioFormat TEST_AUDIO_FORMAT = new AudioFormat.Builder().setSampleRate(
+ TEST_SAMPLE_RATE).setEncoding(AudioFormat.ENCODING_PCM_16BIT).setChannelMask(
+ AudioFormat.CHANNEL_OUT_STEREO).build();
+ private static final int TEST_BUFFER_SIZE = AudioTrack.getMinBufferSize(
+ TEST_AUDIO_FORMAT.getSampleRate(),
+ TEST_AUDIO_FORMAT.getChannelMask(), TEST_AUDIO_FORMAT.getEncoding());
+
+ @Test
+ public void testBuilderConstructionWithContext_defaultDeviceExplicitSessionId() {
+ Context mockDefaultDeviceContext = getVirtualDeviceMockContext(
+ DEVICE_ID_DEFAULT, /*vdm=*/null);
+ int sessionId = getContext().getSystemService(AudioManager.class).generateAudioSessionId();
+
+ AudioTrack audioTrack = new AudioTrack.Builder().setContext(
+ mockDefaultDeviceContext).setAudioAttributes(TEST_AUDIO_ATTRIBUTES).setAudioFormat(
+ TEST_AUDIO_FORMAT).setBufferSizeInBytes(TEST_BUFFER_SIZE).setSessionId(
+ sessionId).build();
+
+ assertEquals(AudioTrack.STATE_INITIALIZED, audioTrack.getState());
+ assertEquals(sessionId, audioTrack.getAudioSessionId());
+ }
+
+ @Test
+ public void testBuilderConstructionWithContext_virtualDeviceDefaultAudioPolicy() {
+ int vdmPlaybackSessionId = getContext().getSystemService(
+ AudioManager.class).generateAudioSessionId();
+ VirtualDeviceManager mockVdm = getMockVirtualDeviceManager(TEST_VIRTUAL_DEVICE_ID,
+ vdmPlaybackSessionId, DEVICE_POLICY_DEFAULT);
+ Context virtualDeviceContext = getVirtualDeviceMockContext(TEST_VIRTUAL_DEVICE_ID, mockVdm);
+
+ AudioTrack audioTrack = new AudioTrack.Builder().setContext(
+ virtualDeviceContext).setAudioAttributes(TEST_AUDIO_ATTRIBUTES).setAudioFormat(
+ TEST_AUDIO_FORMAT).setBufferSizeInBytes(TEST_BUFFER_SIZE).build();
+
+ assertEquals(AudioTrack.STATE_INITIALIZED, audioTrack.getState());
+ assertNotEquals(vdmPlaybackSessionId, audioTrack.getAudioSessionId());
+ }
+
+ @Test
+ public void testBuilderConstructionWithContext_virtualDeviceCustomAudioPolicy() {
+ int vdmPlaybackSessionId = getContext().getSystemService(
+ AudioManager.class).generateAudioSessionId();
+ VirtualDeviceManager mockVdm = getMockVirtualDeviceManager(TEST_VIRTUAL_DEVICE_ID,
+ vdmPlaybackSessionId, DEVICE_POLICY_CUSTOM);
+ Context virtualDeviceContext = getVirtualDeviceMockContext(TEST_VIRTUAL_DEVICE_ID, mockVdm);
+
+ AudioTrack audioTrack = new AudioTrack.Builder().setContext(
+ virtualDeviceContext).setAudioAttributes(TEST_AUDIO_ATTRIBUTES).setAudioFormat(
+ TEST_AUDIO_FORMAT).setBufferSizeInBytes(TEST_BUFFER_SIZE).build();
+
+ assertEquals(AudioTrack.STATE_INITIALIZED, audioTrack.getState());
+ assertEquals(vdmPlaybackSessionId, audioTrack.getAudioSessionId());
+ }
+
+ @Test
+ public void testBuilderConstructionWithContext_virtualDeviceSetSessionIdOverridesContext() {
+ int vdmPlaybackSessionId = getContext().getSystemService(
+ AudioManager.class).generateAudioSessionId();
+ int anotherSessionId = getContext().getSystemService(
+ AudioManager.class).generateAudioSessionId();
+ VirtualDeviceManager mockVdm = getMockVirtualDeviceManager(TEST_VIRTUAL_DEVICE_ID,
+ vdmPlaybackSessionId, DEVICE_POLICY_CUSTOM);
+ Context virtualDeviceContext = getVirtualDeviceMockContext(TEST_VIRTUAL_DEVICE_ID, mockVdm);
+
+ AudioTrack audioTrack = new AudioTrack.Builder().setContext(
+ virtualDeviceContext).setAudioAttributes(
+ TEST_AUDIO_ATTRIBUTES).setAudioFormat(
+ TEST_AUDIO_FORMAT).setBufferSizeInBytes(TEST_BUFFER_SIZE).setSessionId(
+ anotherSessionId).build();
+
+ assertEquals(AudioTrack.STATE_INITIALIZED, audioTrack.getState());
+ assertEquals(anotherSessionId, audioTrack.getAudioSessionId());
+ }
+
+ private Context getContext() {
+ return InstrumentationRegistry.getInstrumentation().getContext();
+ }
+
+ private static Context getVirtualDeviceMockContext(int deviceId, VirtualDeviceManager vdm) {
+ MockContext mockContext = mock(MockContext.class);
+ when(mockContext.getDeviceId()).thenReturn(deviceId);
+ when(mockContext.getSystemService(VirtualDeviceManager.class)).thenReturn(vdm);
+ return mockContext;
+ }
+
+ private static VirtualDeviceManager getMockVirtualDeviceManager(int deviceId,
+ int playbackSessionId, int audioDevicePolicy) {
+ VirtualDeviceManager vdmMock = mock(VirtualDeviceManager.class);
+ when(vdmMock.getAudioPlaybackSessionId(anyInt())).thenReturn(AUDIO_SESSION_ID_GENERATE);
+ when(vdmMock.getAudioPlaybackSessionId(deviceId)).thenReturn(playbackSessionId);
+ when(vdmMock.getDevicePolicy(anyInt(), anyInt())).thenReturn(DEVICE_POLICY_DEFAULT);
+ when(vdmMock.getDevicePolicy(deviceId, POLICY_TYPE_AUDIO)).thenReturn(audioDevicePolicy);
+ return vdmMock;
+ }
+}
diff --git a/packages/BackupRestoreConfirmation/res/values-b+sr+Latn/strings.xml b/packages/BackupRestoreConfirmation/res/values-b+sr+Latn/strings.xml
index ab55120..e7bdd2f 100644
--- a/packages/BackupRestoreConfirmation/res/values-b+sr+Latn/strings.xml
+++ b/packages/BackupRestoreConfirmation/res/values-b+sr+Latn/strings.xml
@@ -16,23 +16,23 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="backup_confirm_title" msgid="827563724209303345">"Rezervna kopije svih podataka"</string>
- <string name="restore_confirm_title" msgid="5469365809567486602">"Potpuno vraćanje"</string>
- <string name="backup_confirm_text" msgid="1878021282758896593">"Zahtevana je potpuna rezervna kopija svih podataka na povezani računar. Da li želite da dozvolite to?\n\nAko niste lično zahtevali rezervnu kopiju, ne dozvoljavajte nastavak radnje."</string>
- <string name="allow_backup_button_label" msgid="4217228747769644068">"Napravi rezervnu kopiju mojih podataka"</string>
- <string name="deny_backup_button_label" msgid="6009119115581097708">"Ne pravi rezervne kopije"</string>
- <string name="restore_confirm_text" msgid="7499866728030461776">"Zahtevano je potpuno vraćanje svih podataka sa povezanog računara. Da li želite da dozvolite to?\n\nAko niste lično zahtevali vraćanje, ne dozvoljavajte nastavak radnje. Time ćete zameniti sve podatke koji su trenutno na uređaju!"</string>
- <string name="allow_restore_button_label" msgid="3081286752277127827">"Vrati moje podatke"</string>
- <string name="deny_restore_button_label" msgid="1724367334453104378">"Ne vraćaj"</string>
- <string name="current_password_text" msgid="8268189555578298067">"Unesite trenutnu lozinku rezervne kopije u nastavku:"</string>
- <string name="device_encryption_restore_text" msgid="1570864916855208992">"Unesite lozinku uređaja za šifrovanje u nastavku."</string>
- <string name="device_encryption_backup_text" msgid="5866590762672844664">"Unesite lozinku uređaja za šifrovanje. Ovo će se koristiti i za šifrovanje rezervne arhive."</string>
- <string name="backup_enc_password_text" msgid="4981585714795233099">"Unesite lozinku koju ćete koristiti za šifrovanje podataka potpune rezervne kopije. Ako to polje ostavite prazno, koristiće se trenutna lozinka rezervne kopije:"</string>
- <string name="backup_enc_password_optional" msgid="1350137345907579306">"Ako želite da šifrujete podatke potpune rezervne kopije, unesite lozinku u nastavku."</string>
- <string name="restore_enc_password_text" msgid="6140898525580710823">"Ako su podaci za vraćanje šifrovani, unesite lozinku u nastavku:"</string>
- <string name="toast_backup_started" msgid="550354281452756121">"Pokretanje pravljenja rezervne kopije..."</string>
- <string name="toast_backup_ended" msgid="3818080769548726424">"Rezervna kopija je napravljena"</string>
- <string name="toast_restore_started" msgid="7881679218971277385">"Pokretanje vraćanja..."</string>
- <string name="toast_restore_ended" msgid="1764041639199696132">"Vraćanje je završeno"</string>
- <string name="toast_timeout" msgid="5276598587087626877">"Vreme za radnju je isteklo"</string>
+ <string name="backup_confirm_title" msgid="827563724209303345">"Резервна копије свих података"</string>
+ <string name="restore_confirm_title" msgid="5469365809567486602">"Потпуно враћање"</string>
+ <string name="backup_confirm_text" msgid="1878021282758896593">"Захтевана је потпуна резервна копија свих података на повезани рачунар. Да ли желите да дозволите то?\n\nАко нисте лично захтевали резервну копију, не дозвољавајте наставак радње."</string>
+ <string name="allow_backup_button_label" msgid="4217228747769644068">"Направи резервну копију мојих података"</string>
+ <string name="deny_backup_button_label" msgid="6009119115581097708">"Не прави резервне копије"</string>
+ <string name="restore_confirm_text" msgid="7499866728030461776">"Захтевано је потпуно враћање свих података са повезаног рачунара. Да ли желите да дозволите то?\n\nАко нисте лично захтевали враћање, не дозвољавајте наставак радње. Тиме ћете заменити све податке који су тренутно на уређају!"</string>
+ <string name="allow_restore_button_label" msgid="3081286752277127827">"Врати моје податке"</string>
+ <string name="deny_restore_button_label" msgid="1724367334453104378">"Не враћај"</string>
+ <string name="current_password_text" msgid="8268189555578298067">"Унесите тренутну лозинку резервне копије у наставку:"</string>
+ <string name="device_encryption_restore_text" msgid="1570864916855208992">"Унесите лозинку уређаја за шифровање у наставку."</string>
+ <string name="device_encryption_backup_text" msgid="5866590762672844664">"Унесите лозинку уређаја за шифровање. Ово ће се користити и за шифровање резервне архиве."</string>
+ <string name="backup_enc_password_text" msgid="4981585714795233099">"Унесите лозинку коју ћете користити за шифровање података потпуне резервне копије. Ако то поље оставите празно, користиће се тренутна лозинка резервне копије:"</string>
+ <string name="backup_enc_password_optional" msgid="1350137345907579306">"Ако желите да шифрујете податке потпуне резервне копије, унесите лозинку у наставку."</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_restore_started" msgid="7881679218971277385">"Покретање враћања..."</string>
+ <string name="toast_restore_ended" msgid="1764041639199696132">"Враћање је завршено"</string>
+ <string name="toast_timeout" msgid="5276598587087626877">"Време за радњу је истекло"</string>
</resources>
diff --git a/packages/CarrierDefaultApp/AndroidManifest.xml b/packages/CarrierDefaultApp/AndroidManifest.xml
index c4bb17c..3f86aba 100644
--- a/packages/CarrierDefaultApp/AndroidManifest.xml
+++ b/packages/CarrierDefaultApp/AndroidManifest.xml
@@ -77,6 +77,7 @@
<receiver android:name="com.android.carrierdefaultapp.SlicePurchaseBroadcastReceiver"
android:exported="true">
<intent-filter>
+ <action android:name="android.intent.action.LOCALE_CHANGED" />
<action android:name="com.android.phone.slice.action.START_SLICE_PURCHASE_APP" />
<action android:name="com.android.phone.slice.action.SLICE_PURCHASE_APP_RESPONSE_TIMEOUT" />
<action android:name="com.android.phone.slice.action.NOTIFICATION_CANCELED" />
diff --git a/packages/CarrierDefaultApp/res/values-am/strings.xml b/packages/CarrierDefaultApp/res/values-am/strings.xml
index 0efdbc4..ae87b17 100644
--- a/packages/CarrierDefaultApp/res/values-am/strings.xml
+++ b/packages/CarrierDefaultApp/res/values-am/strings.xml
@@ -14,16 +14,10 @@
<string name="ssl_error_warning" msgid="3127935140338254180">"ለመቀላቀል እየሞከሩ ያሉት አውታረ መረብ የደህንነት ችግሮች አሉበት።"</string>
<string name="ssl_error_example" msgid="6188711843183058764">"ለምሳሌ፣ የመግቢያ ገጹ የሚታየው ድርጅት ላይሆን ይችላል።"</string>
<string name="ssl_error_continue" msgid="1138548463994095584">"ለማንኛውም በአሳሽ በኩል ይቀጥሉ"</string>
- <!-- no translation found for network_boost_notification_channel (5430986172506159199) -->
- <skip />
- <!-- no translation found for network_boost_notification_title (8226368121348880044) -->
- <skip />
- <!-- no translation found for network_boost_notification_detail (3812434025544196192) -->
- <skip />
- <!-- no translation found for network_boost_notification_button_not_now (4129218252146702688) -->
- <skip />
- <!-- no translation found for network_boost_notification_button_manage (1511552684142641182) -->
- <skip />
- <!-- no translation found for slice_purchase_app_label (915654761797446390) -->
- <skip />
+ <string name="network_boost_notification_channel" msgid="5430986172506159199">"የአውታረ መረብ መጨመር"</string>
+ <string name="network_boost_notification_title" msgid="8226368121348880044">"%s ውሂብን መጨመር ይመክራል"</string>
+ <string name="network_boost_notification_detail" msgid="3812434025544196192">"ለተሻለ አፈጻጸም የአውታረ መረብ መጨመርን ይግዙ"</string>
+ <string name="network_boost_notification_button_not_now" msgid="4129218252146702688">"አሁን አይደለም"</string>
+ <string name="network_boost_notification_button_manage" msgid="1511552684142641182">"አስተዳድር"</string>
+ <string name="slice_purchase_app_label" msgid="915654761797446390">"የአውታረ መረብ መጨመርን ይግዙ"</string>
</resources>
diff --git a/packages/CarrierDefaultApp/res/values-ar/strings.xml b/packages/CarrierDefaultApp/res/values-ar/strings.xml
index 1194e15..ed0a711 100644
--- a/packages/CarrierDefaultApp/res/values-ar/strings.xml
+++ b/packages/CarrierDefaultApp/res/values-ar/strings.xml
@@ -8,9 +8,7 @@
<string name="portal_notification_detail" msgid="2295729385924660881">"النقر للانتقال إلى موقع %s الإلكتروني"</string>
<string name="no_data_notification_detail" msgid="3112125343857014825">"يُرجى الاتصال بمقدم الخدمة %s"</string>
<string name="no_mobile_data_connection_title" msgid="7449525772416200578">"لا يوجد اتصال بيانات الجوال"</string>
- <!-- String.format failed for translation -->
- <!-- no translation found for no_mobile_data_connection (544980465184147010) -->
- <skip />
+ <string name="no_mobile_data_connection" msgid="544980465184147010">"إضافة بيانات أو خطة تجوال خلال %s"</string>
<string name="mobile_data_status_notification_channel_name" msgid="833999690121305708">"حالة بيانات الجوّال"</string>
<string name="action_bar_label" msgid="4290345990334377177">"تسجيل الدخول إلى شبكة الجوّال"</string>
<string name="ssl_error_warning" msgid="3127935140338254180">"الشبكة التي تحاول الانضمام إليها بها مشاكل أمنية."</string>
diff --git a/packages/CarrierDefaultApp/res/values-b+sr+Latn/strings.xml b/packages/CarrierDefaultApp/res/values-b+sr+Latn/strings.xml
index a1974f0..84db181b 100644
--- a/packages/CarrierDefaultApp/res/values-b+sr+Latn/strings.xml
+++ b/packages/CarrierDefaultApp/res/values-b+sr+Latn/strings.xml
@@ -2,22 +2,22 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_name" msgid="5247871339820894594">"CarrierDefaultApp"</string>
- <string name="android_system_label" msgid="2797790869522345065">"Mobilni operater"</string>
- <string name="portal_notification_id" msgid="5155057562457079297">"Mobilni podaci su potrošeni"</string>
- <string name="no_data_notification_id" msgid="668400731803969521">"Mobilni podaci su deaktivirani"</string>
- <string name="portal_notification_detail" msgid="2295729385924660881">"Dodirnite da biste posetili veb-sajt %s"</string>
- <string name="no_data_notification_detail" msgid="3112125343857014825">"Kontaktirajte dobavljača usluge %s"</string>
- <string name="no_mobile_data_connection_title" msgid="7449525772416200578">"Nema veze za prenos podataka preko mobilnog operatera"</string>
- <string name="no_mobile_data_connection" msgid="544980465184147010">"Dodajte podatke ili paket za roming preko operatera %s"</string>
- <string name="mobile_data_status_notification_channel_name" msgid="833999690121305708">"Status mobilnih podataka"</string>
- <string name="action_bar_label" msgid="4290345990334377177">"Prijavite se na mobilnu mrežu"</string>
- <string name="ssl_error_warning" msgid="3127935140338254180">"Mreža kojoj pokušavate da se pridružite ima bezbednosnih problema."</string>
- <string name="ssl_error_example" msgid="6188711843183058764">"Na primer, stranica za prijavljivanje možda ne pripada prikazanoj organizaciji."</string>
- <string name="ssl_error_continue" msgid="1138548463994095584">"Ipak nastavi preko pregledača"</string>
- <string name="network_boost_notification_channel" msgid="5430986172506159199">"Pojačanje mreže"</string>
- <string name="network_boost_notification_title" msgid="8226368121348880044">"%s preporučuje povećanje podataka"</string>
- <string name="network_boost_notification_detail" msgid="3812434025544196192">"Kupite pojačanje mreže za bolji učinak"</string>
- <string name="network_boost_notification_button_not_now" msgid="4129218252146702688">"Ne sada"</string>
- <string name="network_boost_notification_button_manage" msgid="1511552684142641182">"Upravljajte"</string>
- <string name="slice_purchase_app_label" msgid="915654761797446390">"Kupite pojačanje mreže."</string>
+ <string name="android_system_label" msgid="2797790869522345065">"Мобилни оператер"</string>
+ <string name="portal_notification_id" msgid="5155057562457079297">"Мобилни подаци су потрошени"</string>
+ <string name="no_data_notification_id" msgid="668400731803969521">"Мобилни подаци су деактивирани"</string>
+ <string name="portal_notification_detail" msgid="2295729385924660881">"Додирните да бисте посетили веб-сајт %s"</string>
+ <string name="no_data_notification_detail" msgid="3112125343857014825">"Контактирајте добављача услуге %s"</string>
+ <string name="no_mobile_data_connection_title" msgid="7449525772416200578">"Нема везе за пренос података преко мобилног оператера"</string>
+ <string name="no_mobile_data_connection" msgid="544980465184147010">"Додајте податке или пакет за роминг преко оператера %s"</string>
+ <string name="mobile_data_status_notification_channel_name" msgid="833999690121305708">"Статус мобилних података"</string>
+ <string name="action_bar_label" msgid="4290345990334377177">"Пријавите се на мобилну мрежу"</string>
+ <string name="ssl_error_warning" msgid="3127935140338254180">"Мрежа којој покушавате да се придружите има безбедносних проблема."</string>
+ <string name="ssl_error_example" msgid="6188711843183058764">"На пример, страница за пријављивање можда не припада приказаној организацији."</string>
+ <string name="ssl_error_continue" msgid="1138548463994095584">"Ипак настави преко прегледача"</string>
+ <string name="network_boost_notification_channel" msgid="5430986172506159199">"Појачање мреже"</string>
+ <string name="network_boost_notification_title" msgid="8226368121348880044">"%s препоручује повећање података"</string>
+ <string name="network_boost_notification_detail" msgid="3812434025544196192">"Купите појачање мреже за бољи учинак"</string>
+ <string name="network_boost_notification_button_not_now" msgid="4129218252146702688">"Не сада"</string>
+ <string name="network_boost_notification_button_manage" msgid="1511552684142641182">"Управљајте"</string>
+ <string name="slice_purchase_app_label" msgid="915654761797446390">"Купите појачање мреже."</string>
</resources>
diff --git a/packages/CarrierDefaultApp/res/values-te/strings.xml b/packages/CarrierDefaultApp/res/values-te/strings.xml
index d1e49ca..a6113ae 100644
--- a/packages/CarrierDefaultApp/res/values-te/strings.xml
+++ b/packages/CarrierDefaultApp/res/values-te/strings.xml
@@ -13,7 +13,7 @@
<string name="action_bar_label" msgid="4290345990334377177">"మొబైల్ నెట్వర్క్కి సైన్ ఇన్ చేయండి"</string>
<string name="ssl_error_warning" msgid="3127935140338254180">"మీరు చేరడానికి ప్రయత్నిస్తున్న నెట్వర్క్ భద్రతా సమస్యలను కలిగి ఉంది."</string>
<string name="ssl_error_example" msgid="6188711843183058764">"ఉదాహరణకు, లాగిన్ పేజీ చూపిన సంస్థకు చెందినది కాకపోవచ్చు."</string>
- <string name="ssl_error_continue" msgid="1138548463994095584">"ఏదేమైనా బ్రౌజర్ ద్వారా కొనసాగించు"</string>
+ <string name="ssl_error_continue" msgid="1138548463994095584">"ఏదేమైనా బ్రౌజర్ ద్వారా కొనసాగించండి"</string>
<string name="network_boost_notification_channel" msgid="5430986172506159199">"నెట్వర్క్ బూస్ట్"</string>
<string name="network_boost_notification_title" msgid="8226368121348880044">"డేటా బూస్ట్ను %s సిఫార్సు చేస్తోంది"</string>
<string name="network_boost_notification_detail" msgid="3812434025544196192">"మెరుగైన పనితీరు కోసం నెట్వర్క్ బూస్ట్ను కొనుగోలు చేయండి"</string>
diff --git a/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/SlicePurchaseActivity.java b/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/SlicePurchaseActivity.java
index c524037..c08b83a 100644
--- a/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/SlicePurchaseActivity.java
+++ b/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/SlicePurchaseActivity.java
@@ -19,21 +19,17 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.Activity;
-import android.app.NotificationManager;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
-import android.telephony.CarrierConfigManager;
import android.telephony.SubscriptionManager;
import android.telephony.TelephonyManager;
import android.util.Log;
import android.view.KeyEvent;
-import android.webkit.URLUtil;
import android.webkit.WebView;
import com.android.phone.slice.SlicePurchaseController;
-import java.net.MalformedURLException;
import java.net.URL;
import java.util.concurrent.TimeUnit;
@@ -62,38 +58,29 @@
@NonNull private WebView mWebView;
@NonNull private Context mApplicationContext;
@NonNull private Intent mIntent;
- @Nullable private URL mUrl;
- private int mSubId;
+ @NonNull private URL mUrl;
@TelephonyManager.PremiumCapability protected int mCapability;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mIntent = getIntent();
- mSubId = mIntent.getIntExtra(SlicePurchaseController.EXTRA_SUB_ID,
+ int subId = mIntent.getIntExtra(SlicePurchaseController.EXTRA_SUB_ID,
SubscriptionManager.INVALID_SUBSCRIPTION_ID);
mCapability = mIntent.getIntExtra(SlicePurchaseController.EXTRA_PREMIUM_CAPABILITY,
SlicePurchaseController.PREMIUM_CAPABILITY_INVALID);
+ String url = mIntent.getStringExtra(SlicePurchaseController.EXTRA_PURCHASE_URL);
mApplicationContext = getApplicationContext();
- mUrl = getUrl();
- logd("onCreate: subId=" + mSubId + ", capability="
- + TelephonyManager.convertPremiumCapabilityToString(mCapability)
- + ", url=" + mUrl);
+ logd("onCreate: subId=" + subId + ", capability="
+ + TelephonyManager.convertPremiumCapabilityToString(mCapability) + ", url=" + url);
// Cancel network boost notification
- mApplicationContext.getSystemService(NotificationManager.class)
- .cancel(SlicePurchaseBroadcastReceiver.NETWORK_BOOST_NOTIFICATION_TAG, mCapability);
+ SlicePurchaseBroadcastReceiver.cancelNotification(mApplicationContext, mCapability);
- // Verify intent and values are valid
- if (!SlicePurchaseBroadcastReceiver.isIntentValid(mIntent)) {
- loge("Not starting SlicePurchaseActivity with an invalid Intent: " + mIntent);
- SlicePurchaseBroadcastReceiver.sendSlicePurchaseAppResponse(
- mIntent, SlicePurchaseController.EXTRA_INTENT_REQUEST_FAILED);
- finishAndRemoveTask();
- return;
- }
+ // Verify purchase URL is valid
+ mUrl = SlicePurchaseBroadcastReceiver.getPurchaseUrl(url);
if (mUrl == null) {
- String error = "Unable to create a URL from carrier configs.";
+ String error = "Unable to create a purchase URL.";
loge(error);
Intent data = new Intent();
data.putExtra(SlicePurchaseController.EXTRA_FAILURE_CODE,
@@ -104,18 +91,26 @@
finishAndRemoveTask();
return;
}
- if (mSubId != SubscriptionManager.getDefaultSubscriptionId()) {
+
+ // Verify intent is valid
+ if (!SlicePurchaseBroadcastReceiver.isIntentValid(mIntent)) {
+ loge("Not starting SlicePurchaseActivity with an invalid Intent: " + mIntent);
+ SlicePurchaseBroadcastReceiver.sendSlicePurchaseAppResponse(
+ mIntent, SlicePurchaseController.EXTRA_INTENT_REQUEST_FAILED);
+ finishAndRemoveTask();
+ return;
+ }
+
+ // Verify sub ID is valid
+ if (subId != SubscriptionManager.getDefaultSubscriptionId()) {
loge("Unable to start the slice purchase application on the non-default data "
- + "subscription: " + mSubId);
+ + "subscription: " + subId);
SlicePurchaseBroadcastReceiver.sendSlicePurchaseAppResponse(
mIntent, SlicePurchaseController.EXTRA_INTENT_NOT_DEFAULT_DATA_SUBSCRIPTION);
finishAndRemoveTask();
return;
}
- // Create a reference to this activity in SlicePurchaseBroadcastReceiver
- SlicePurchaseBroadcastReceiver.updateSlicePurchaseActivity(mCapability, this);
-
// Create and configure WebView
setupWebView();
}
@@ -161,28 +156,9 @@
logd("onDestroy: User canceled the purchase by closing the application.");
SlicePurchaseBroadcastReceiver.sendSlicePurchaseAppResponse(
mIntent, SlicePurchaseController.EXTRA_INTENT_CANCELED);
- SlicePurchaseBroadcastReceiver.removeSlicePurchaseActivity(mCapability);
super.onDestroy();
}
- @Nullable private URL getUrl() {
- String url = mApplicationContext.getSystemService(CarrierConfigManager.class)
- .getConfigForSubId(mSubId).getString(
- CarrierConfigManager.KEY_PREMIUM_CAPABILITY_PURCHASE_URL_STRING);
- boolean isUrlValid = URLUtil.isValidUrl(url);
- if (URLUtil.isAssetUrl(url)) {
- isUrlValid = url.equals(SlicePurchaseController.SLICE_PURCHASE_TEST_FILE);
- }
- if (isUrlValid) {
- try {
- return new URL(url);
- } catch (MalformedURLException ignored) {
- }
- }
- loge("Invalid URL: " + url);
- return null;
- }
-
private void setupWebView() {
// Create WebView
mWebView = new WebView(this);
diff --git a/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/SlicePurchaseBroadcastReceiver.java b/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/SlicePurchaseBroadcastReceiver.java
index 367ae06..3041138 100644
--- a/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/SlicePurchaseBroadcastReceiver.java
+++ b/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/SlicePurchaseBroadcastReceiver.java
@@ -16,6 +16,7 @@
package com.android.carrierdefaultapp;
import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.app.Notification;
import android.app.NotificationChannel;
import android.app.NotificationManager;
@@ -24,20 +25,28 @@
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
+import android.content.res.Configuration;
+import android.content.res.Resources;
import android.graphics.drawable.Icon;
+import android.os.LocaleList;
+import android.os.SystemProperties;
import android.os.UserHandle;
import android.telephony.AnomalyReporter;
import android.telephony.SubscriptionManager;
import android.telephony.TelephonyManager;
import android.text.TextUtils;
import android.util.Log;
+import android.webkit.URLUtil;
import android.webkit.WebView;
import com.android.internal.annotations.VisibleForTesting;
import com.android.phone.slice.SlicePurchaseController;
-import java.lang.ref.WeakReference;
+import java.net.MalformedURLException;
+import java.net.URISyntaxException;
+import java.net.URL;
import java.util.HashMap;
+import java.util.Locale;
import java.util.Map;
import java.util.UUID;
@@ -57,10 +66,6 @@
*/
private static final String UUID_BAD_PENDING_INTENT = "c360246e-95dc-4abf-9dc1-929a76cd7e53";
- /** Weak references to {@link SlicePurchaseActivity} for each capability, if it exists. */
- private static final Map<Integer, WeakReference<SlicePurchaseActivity>>
- sSlicePurchaseActivities = new HashMap<>();
-
/** Channel ID for the network boost notification. */
private static final String NETWORK_BOOST_NOTIFICATION_CHANNEL_ID = "network_boost";
/** Tag for the network boost notification. */
@@ -70,27 +75,28 @@
"com.android.phone.slice.action.NOTIFICATION_CANCELED";
/**
- * Create a weak reference to {@link SlicePurchaseActivity}. The reference will be removed when
- * {@link SlicePurchaseActivity#onDestroy()} is called.
- *
- * @param capability The premium capability requested.
- * @param slicePurchaseActivity The instance of SlicePurchaseActivity.
+ * A map of Intents sent by {@link SlicePurchaseController} for each capability.
+ * If this map contains an Intent for a given capability, the network boost notification to
+ * purchase the capability is visible to the user.
+ * If this map does not contain an Intent for a given capability, either the capability was
+ * never requested or the {@link SlicePurchaseActivity} is visible to the user.
+ * An Intent is added to this map when the network boost notification is displayed to the user
+ * and removed from the map when the notification is canceled.
*/
- public static void updateSlicePurchaseActivity(
- @TelephonyManager.PremiumCapability int capability,
- @NonNull SlicePurchaseActivity slicePurchaseActivity) {
- sSlicePurchaseActivities.put(capability, new WeakReference<>(slicePurchaseActivity));
- }
+ private static final Map<Integer, Intent> sIntents = new HashMap<>();
/**
- * Remove the weak reference to {@link SlicePurchaseActivity} when
- * {@link SlicePurchaseActivity#onDestroy()} is called.
+ * Cancel the network boost notification for the given capability and
+ * remove the corresponding notification intent from the map.
*
- * @param capability The premium capability requested.
+ * @param context The context to cancel the notification in.
+ * @param capability The premium capability to cancel the notification for.
*/
- public static void removeSlicePurchaseActivity(
+ public static void cancelNotification(@NonNull Context context,
@TelephonyManager.PremiumCapability int capability) {
- sSlicePurchaseActivities.remove(capability);
+ context.getSystemService(NotificationManager.class).cancelAsUser(
+ NETWORK_BOOST_NOTIFICATION_TAG, capability, UserHandle.ALL);
+ sIntents.remove(capability);
}
/**
@@ -139,7 +145,7 @@
* Check whether the Intent is valid and can be used to complete purchases in the slice purchase
* application. This checks that all necessary extras exist and that the values are valid.
*
- * @param intent The intent to check
+ * @param intent The intent to check.
* @return {@code true} if the intent is valid and {@code false} otherwise.
*/
public static boolean isIntentValid(@NonNull Intent intent) {
@@ -164,6 +170,12 @@
return false;
}
+ String purchaseUrl = intent.getStringExtra(SlicePurchaseController.EXTRA_PURCHASE_URL);
+ if (getPurchaseUrl(purchaseUrl) == null) {
+ loge("isIntentValid: invalid purchase URL: " + purchaseUrl);
+ return false;
+ }
+
String appName = intent.getStringExtra(SlicePurchaseController.EXTRA_REQUESTING_APP_NAME);
if (TextUtils.isEmpty(appName)) {
loge("isIntentValid: empty requesting application name: " + appName);
@@ -180,6 +192,30 @@
SlicePurchaseController.EXTRA_INTENT_NOTIFICATION_SHOWN);
}
+ /**
+ * Get the {@link URL} from the given purchase URL String, if it is valid.
+ *
+ * @param purchaseUrl The purchase URL String to use to create the URL.
+ * @return The purchase URL from the given String or {@code null} if it is invalid.
+ */
+ @Nullable public static URL getPurchaseUrl(@Nullable String purchaseUrl) {
+ if (!URLUtil.isValidUrl(purchaseUrl)) {
+ return null;
+ }
+ if (URLUtil.isAssetUrl(purchaseUrl)
+ && !purchaseUrl.equals(SlicePurchaseController.SLICE_PURCHASE_TEST_FILE)) {
+ return null;
+ }
+ URL url = null;
+ try {
+ url = new URL(purchaseUrl);
+ url.toURI();
+ } catch (MalformedURLException | URISyntaxException e) {
+ loge("Invalid purchase URL: " + purchaseUrl + ", " + e);
+ }
+ return url;
+ }
+
private static boolean isPendingIntentValid(@NonNull Intent intent, @NonNull String extra) {
String intentType = getPendingIntentType(extra);
PendingIntent pendingIntent = intent.getParcelableExtra(extra, PendingIntent.class);
@@ -223,8 +259,11 @@
public void onReceive(@NonNull Context context, @NonNull Intent intent) {
logd("onReceive intent: " + intent.getAction());
switch (intent.getAction()) {
+ case Intent.ACTION_LOCALE_CHANGED:
+ onLocaleChanged(context);
+ break;
case SlicePurchaseController.ACTION_START_SLICE_PURCHASE_APP:
- onDisplayNetworkBoostNotification(context, intent);
+ onDisplayNetworkBoostNotification(context, intent, false);
break;
case SlicePurchaseController.ACTION_SLICE_PURCHASE_APP_RESPONSE_TIMEOUT:
onTimeout(context, intent);
@@ -237,17 +276,31 @@
}
}
+ private void onLocaleChanged(@NonNull Context context) {
+ if (sIntents.isEmpty()) return;
+
+ for (int capability : new int[]{TelephonyManager.PREMIUM_CAPABILITY_PRIORITIZE_LATENCY}) {
+ if (sIntents.get(capability) != null) {
+ // Notification is active -- update notification for new locale
+ context.getSystemService(NotificationManager.class).cancelAsUser(
+ NETWORK_BOOST_NOTIFICATION_TAG, capability, UserHandle.ALL);
+ onDisplayNetworkBoostNotification(context, sIntents.get(capability), true);
+ }
+ }
+ }
+
private void onDisplayNetworkBoostNotification(@NonNull Context context,
- @NonNull Intent intent) {
- if (!isIntentValid(intent)) {
+ @NonNull Intent intent, boolean repeat) {
+ if (!repeat && !isIntentValid(intent)) {
sendSlicePurchaseAppResponse(intent,
SlicePurchaseController.EXTRA_INTENT_REQUEST_FAILED);
return;
}
+ Resources res = getResources(context);
NotificationChannel channel = new NotificationChannel(
NETWORK_BOOST_NOTIFICATION_CHANNEL_ID,
- context.getResources().getString(R.string.network_boost_notification_channel),
+ res.getString(R.string.network_boost_notification_channel),
NotificationManager.IMPORTANCE_DEFAULT);
// CarrierDefaultApp notifications are unblockable by default. Make this channel blockable
// to allow users to disable notifications posted to this channel without affecting other
@@ -257,12 +310,11 @@
Notification notification =
new Notification.Builder(context, NETWORK_BOOST_NOTIFICATION_CHANNEL_ID)
- .setContentTitle(String.format(context.getResources().getString(
+ .setContentTitle(String.format(res.getString(
R.string.network_boost_notification_title),
intent.getStringExtra(
SlicePurchaseController.EXTRA_REQUESTING_APP_NAME)))
- .setContentText(context.getResources().getString(
- R.string.network_boost_notification_detail))
+ .setContentText(res.getString(R.string.network_boost_notification_detail))
.setSmallIcon(R.drawable.ic_network_boost)
.setContentIntent(createContentIntent(context, intent, 1))
.setDeleteIntent(intent.getParcelableExtra(
@@ -271,26 +323,56 @@
// the user canceling or closing the notification.
.addAction(new Notification.Action.Builder(
Icon.createWithResource(context, R.drawable.ic_network_boost),
- context.getResources().getString(
- R.string.network_boost_notification_button_not_now),
+ res.getString(R.string.network_boost_notification_button_not_now),
createCanceledIntent(context, intent)).build())
// Add an action for the "Manage" button, which has the same behavior as
// the user clicking on the notification.
.addAction(new Notification.Action.Builder(
Icon.createWithResource(context, R.drawable.ic_network_boost),
- context.getResources().getString(
- R.string.network_boost_notification_button_manage),
+ res.getString(R.string.network_boost_notification_button_manage),
createContentIntent(context, intent, 2)).build())
.build();
int capability = intent.getIntExtra(SlicePurchaseController.EXTRA_PREMIUM_CAPABILITY,
SlicePurchaseController.PREMIUM_CAPABILITY_INVALID);
- logd("Display the network boost notification for capability "
+ logd((repeat ? "Update" : "Display") + " the network boost notification for capability "
+ TelephonyManager.convertPremiumCapabilityToString(capability));
context.getSystemService(NotificationManager.class).notifyAsUser(
NETWORK_BOOST_NOTIFICATION_TAG, capability, notification, UserHandle.ALL);
- sendSlicePurchaseAppResponse(intent,
- SlicePurchaseController.EXTRA_INTENT_NOTIFICATION_SHOWN);
+ if (!repeat) {
+ sIntents.put(capability, intent);
+ sendSlicePurchaseAppResponse(intent,
+ SlicePurchaseController.EXTRA_INTENT_NOTIFICATION_SHOWN);
+ }
+ }
+
+ /**
+ * Get the {@link Resources} for the current locale.
+ *
+ * @param context The context to get the resources in.
+ *
+ * @return The resources in the current locale.
+ */
+ @VisibleForTesting
+ @NonNull public Resources getResources(@NonNull Context context) {
+ Resources resources = context.getResources();
+ Configuration config = resources.getConfiguration();
+ config.setLocale(getCurrentLocale());
+ return new Resources(resources.getAssets(), resources.getDisplayMetrics(), config);
+ }
+
+ /**
+ * Get the current {@link Locale} from the system property {@code persist.sys.locale}.
+ *
+ * @return The user's default/preferred language.
+ */
+ @VisibleForTesting
+ @NonNull public Locale getCurrentLocale() {
+ String languageTag = SystemProperties.get("persist.sys.locale");
+ if (TextUtils.isEmpty(languageTag)) {
+ return LocaleList.getAdjustedDefault().get(0);
+ }
+ return Locale.forLanguageTag(languageTag);
}
/**
@@ -343,16 +425,13 @@
SlicePurchaseController.PREMIUM_CAPABILITY_INVALID);
logd("Purchase capability " + TelephonyManager.convertPremiumCapabilityToString(capability)
+ " timed out.");
- if (sSlicePurchaseActivities.get(capability) == null) {
- // Notification is still active
+ if (sIntents.get(capability) != null) {
+ // Notification is still active -- cancel pending notification
logd("Closing network boost notification since the user did not respond in time.");
- context.getSystemService(NotificationManager.class).cancelAsUser(
- NETWORK_BOOST_NOTIFICATION_TAG, capability, UserHandle.ALL);
+ cancelNotification(context, capability);
} else {
- // Notification was dismissed but SlicePurchaseActivity is still active
- logd("Closing slice purchase application WebView since the user did not complete the "
- + "purchase in time.");
- sSlicePurchaseActivities.get(capability).get().finishAndRemoveTask();
+ // SlicePurchaseActivity is still active -- ignore timer
+ logd("Ignoring timeout since the SlicePurchaseActivity is still active.");
}
}
@@ -360,8 +439,7 @@
int capability = intent.getIntExtra(SlicePurchaseController.EXTRA_PREMIUM_CAPABILITY,
SlicePurchaseController.PREMIUM_CAPABILITY_INVALID);
logd("onUserCanceled: " + TelephonyManager.convertPremiumCapabilityToString(capability));
- context.getSystemService(NotificationManager.class)
- .cancelAsUser(NETWORK_BOOST_NOTIFICATION_TAG, capability, UserHandle.ALL);
+ cancelNotification(context, capability);
sendSlicePurchaseAppResponse(intent, SlicePurchaseController.EXTRA_INTENT_CANCELED);
}
diff --git a/packages/CarrierDefaultApp/tests/unit/Android.bp b/packages/CarrierDefaultApp/tests/unit/Android.bp
index cdf7957..0d08ec6 100644
--- a/packages/CarrierDefaultApp/tests/unit/Android.bp
+++ b/packages/CarrierDefaultApp/tests/unit/Android.bp
@@ -37,5 +37,6 @@
// Include all test java files.
srcs: ["src/**/*.java"],
platform_apis: true,
+ use_embedded_native_libs: false,
instrumentation_for: "CarrierDefaultApp",
}
diff --git a/packages/CarrierDefaultApp/tests/unit/AndroidManifest.xml b/packages/CarrierDefaultApp/tests/unit/AndroidManifest.xml
index 7a26d95..995170a 100644
--- a/packages/CarrierDefaultApp/tests/unit/AndroidManifest.xml
+++ b/packages/CarrierDefaultApp/tests/unit/AndroidManifest.xml
@@ -17,7 +17,7 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.android.carrierdefaultapp.tests.unit">
<uses-permission android:name="android.permission.GET_INTENT_SENDER_INTENT" />
- <application>
+ <application android:extractNativeLibs="true">
<uses-library android:name="android.test.runner" />
</application>
diff --git a/packages/CarrierDefaultApp/tests/unit/src/com/android/carrierdefaultapp/SlicePurchaseActivityTest.java b/packages/CarrierDefaultApp/tests/unit/src/com/android/carrierdefaultapp/SlicePurchaseActivityTest.java
index cecc86d..1bf644e 100644
--- a/packages/CarrierDefaultApp/tests/unit/src/com/android/carrierdefaultapp/SlicePurchaseActivityTest.java
+++ b/packages/CarrierDefaultApp/tests/unit/src/com/android/carrierdefaultapp/SlicePurchaseActivityTest.java
@@ -39,7 +39,6 @@
import com.android.phone.slice.SlicePurchaseController;
-import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -94,6 +93,8 @@
SubscriptionManager.getDefaultDataSubscriptionId());
intent.putExtra(SlicePurchaseController.EXTRA_PREMIUM_CAPABILITY,
TelephonyManager.PREMIUM_CAPABILITY_PRIORITIZE_LATENCY);
+ intent.putExtra(SlicePurchaseController.EXTRA_PURCHASE_URL,
+ SlicePurchaseController.SLICE_PURCHASE_TEST_FILE);
intent.putExtra(SlicePurchaseController.EXTRA_REQUESTING_APP_NAME, TAG);
Intent spiedIntent = spy(intent);
@@ -110,12 +111,6 @@
mSlicePurchaseActivity = startActivity(spiedIntent, null, null);
}
- @After
- public void tearDown() throws Exception {
- mSlicePurchaseActivity.onDestroy();
- super.tearDown();
- }
-
@Test
public void testOnPurchaseSuccessful() throws Exception {
int duration = 5 * 60 * 1000; // 5 minutes
diff --git a/packages/CarrierDefaultApp/tests/unit/src/com/android/carrierdefaultapp/SlicePurchaseBroadcastReceiverTest.java b/packages/CarrierDefaultApp/tests/unit/src/com/android/carrierdefaultapp/SlicePurchaseBroadcastReceiverTest.java
index ab99a76..958e138 100644
--- a/packages/CarrierDefaultApp/tests/unit/src/com/android/carrierdefaultapp/SlicePurchaseBroadcastReceiverTest.java
+++ b/packages/CarrierDefaultApp/tests/unit/src/com/android/carrierdefaultapp/SlicePurchaseBroadcastReceiverTest.java
@@ -18,10 +18,14 @@
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
import static org.mockito.Mockito.any;
import static org.mockito.Mockito.anyInt;
import static org.mockito.Mockito.anyString;
+import static org.mockito.Mockito.clearInvocations;
+import static org.mockito.Mockito.doCallRealMethod;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.eq;
import static org.mockito.Mockito.never;
@@ -35,11 +39,11 @@
import android.content.Intent;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
+import android.content.res.Configuration;
import android.content.res.Resources;
import android.os.UserHandle;
import android.telephony.SubscriptionManager;
import android.telephony.TelephonyManager;
-import android.util.DisplayMetrics;
import androidx.test.runner.AndroidJUnit4;
@@ -52,6 +56,9 @@
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
+import java.net.URL;
+import java.util.Locale;
+
@RunWith(AndroidJUnit4.class)
public class SlicePurchaseBroadcastReceiverTest {
private static final int PHONE_ID = 0;
@@ -67,24 +74,26 @@
@Mock PendingIntent mNotificationShownIntent;
@Mock Context mContext;
@Mock Resources mResources;
+ @Mock Configuration mConfiguration;
@Mock NotificationManager mNotificationManager;
@Mock ApplicationInfo mApplicationInfo;
@Mock PackageManager mPackageManager;
- @Mock DisplayMetrics mDisplayMetrics;
- @Mock SlicePurchaseActivity mSlicePurchaseActivity;
private SlicePurchaseBroadcastReceiver mSlicePurchaseBroadcastReceiver;
- private ArgumentCaptor<Intent> mIntentCaptor;
- private ArgumentCaptor<Notification> mNotificationCaptor;
+ private Resources mSpiedResources;
@Before
public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
+ mSpiedResources = spy(Resources.getSystem());
+
+ doReturn("").when(mResources).getString(anyInt());
doReturn(mNotificationManager).when(mContext)
.getSystemService(eq(NotificationManager.class));
+ doReturn(mApplicationInfo).when(mContext).getApplicationInfo();
+ doReturn(mPackageManager).when(mContext).getPackageManager();
+ doReturn(mSpiedResources).when(mContext).getResources();
- mIntentCaptor = ArgumentCaptor.forClass(Intent.class);
- mNotificationCaptor = ArgumentCaptor.forClass(Notification.class);
mSlicePurchaseBroadcastReceiver = spy(new SlicePurchaseBroadcastReceiver());
}
@@ -109,8 +118,9 @@
eq(EXTRA), eq(PendingIntent.class));
SlicePurchaseBroadcastReceiver.sendSlicePurchaseAppResponseWithData(
mContext, mIntent, EXTRA, mDataIntent);
- verify(mPendingIntent).send(eq(mContext), eq(0), mIntentCaptor.capture());
- assertEquals(mDataIntent, mIntentCaptor.getValue());
+ ArgumentCaptor<Intent> captor = ArgumentCaptor.forClass(Intent.class);
+ verify(mPendingIntent).send(eq(mContext), eq(0), captor.capture());
+ assertEquals(mDataIntent, captor.getValue());
}
@Test
@@ -124,6 +134,8 @@
eq(SlicePurchaseController.EXTRA_SUB_ID), anyInt());
doReturn(TelephonyManager.PREMIUM_CAPABILITY_PRIORITIZE_LATENCY).when(mIntent).getIntExtra(
eq(SlicePurchaseController.EXTRA_PREMIUM_CAPABILITY), anyInt());
+ doReturn(SlicePurchaseController.SLICE_PURCHASE_TEST_FILE).when(mIntent).getStringExtra(
+ eq(SlicePurchaseController.EXTRA_PURCHASE_URL));
doReturn(TAG).when(mIntent).getStringExtra(
eq(SlicePurchaseController.EXTRA_REQUESTING_APP_NAME));
assertFalse(SlicePurchaseBroadcastReceiver.isIntentValid(mIntent));
@@ -137,18 +149,53 @@
}
@Test
- public void testDisplayNetworkBoostNotification() throws Exception {
- // set up intent
- doReturn(SlicePurchaseController.ACTION_START_SLICE_PURCHASE_APP).when(mIntent).getAction();
- doReturn(PHONE_ID).when(mIntent).getIntExtra(
- eq(SlicePurchaseController.EXTRA_PHONE_ID), anyInt());
- doReturn(SubscriptionManager.getDefaultDataSubscriptionId()).when(mIntent).getIntExtra(
- eq(SlicePurchaseController.EXTRA_SUB_ID), anyInt());
- doReturn(TelephonyManager.PREMIUM_CAPABILITY_PRIORITIZE_LATENCY).when(mIntent).getIntExtra(
- eq(SlicePurchaseController.EXTRA_PREMIUM_CAPABILITY), anyInt());
- doReturn(TAG).when(mIntent).getStringExtra(
- eq(SlicePurchaseController.EXTRA_REQUESTING_APP_NAME));
+ public void testGetPurchaseUrl() {
+ String[] invalidUrls = new String[] {
+ null,
+ "",
+ "www.google.com",
+ "htt://www.google.com",
+ "http//www.google.com",
+ "http:/www.google.com",
+ "file:///android_asset/",
+ "file:///android_asset/slice_store_test.html"
+ };
+ for (String url : invalidUrls) {
+ URL purchaseUrl = SlicePurchaseBroadcastReceiver.getPurchaseUrl(url);
+ assertNull(purchaseUrl);
+ }
+
+ assertEquals(SlicePurchaseController.SLICE_PURCHASE_TEST_FILE,
+ SlicePurchaseBroadcastReceiver.getPurchaseUrl(
+ SlicePurchaseController.SLICE_PURCHASE_TEST_FILE).toString());
+ }
+
+ @Test
+ public void testDisplayNetworkBoostNotification() throws Exception {
+ displayNetworkBoostNotification();
+
+ // verify network boost notification was shown
+ ArgumentCaptor<Notification> captor = ArgumentCaptor.forClass(Notification.class);
+ verify(mNotificationManager).notifyAsUser(
+ eq(SlicePurchaseBroadcastReceiver.NETWORK_BOOST_NOTIFICATION_TAG),
+ eq(TelephonyManager.PREMIUM_CAPABILITY_PRIORITIZE_LATENCY),
+ captor.capture(),
+ eq(UserHandle.ALL));
+
+ // verify notification fields
+ Notification notification = captor.getValue();
+ assertEquals(mContentIntent1, notification.contentIntent);
+ assertEquals(mPendingIntent, notification.deleteIntent);
+ assertEquals(2, notification.actions.length);
+ assertEquals(mCanceledIntent, notification.actions[0].actionIntent);
+ assertEquals(mContentIntent2, notification.actions[1].actionIntent);
+
+ // verify SlicePurchaseController was notified
+ verify(mNotificationShownIntent).send();
+ }
+
+ private void displayNetworkBoostNotification() {
// set up pending intents
doReturn(TelephonyManager.PHONE_PROCESS_NAME).when(mPendingIntent).getCreatorPackage();
doReturn(true).when(mPendingIntent).isBroadcast();
@@ -161,14 +208,7 @@
eq(SlicePurchaseController.EXTRA_INTENT_NOTIFICATION_SHOWN),
eq(PendingIntent.class));
- // set up notification
- doReturn(mResources).when(mContext).getResources();
- doReturn(mDisplayMetrics).when(mResources).getDisplayMetrics();
- doReturn("").when(mResources).getString(anyInt());
- doReturn(mApplicationInfo).when(mContext).getApplicationInfo();
- doReturn(mPackageManager).when(mContext).getPackageManager();
-
- // set up intents created by broadcast receiver
+ // spy notification intents to prevent PendingIntent issues
doReturn(mContentIntent1).when(mSlicePurchaseBroadcastReceiver).createContentIntent(
eq(mContext), eq(mIntent), eq(1));
doReturn(mContentIntent2).when(mSlicePurchaseBroadcastReceiver).createContentIntent(
@@ -176,35 +216,30 @@
doReturn(mCanceledIntent).when(mSlicePurchaseBroadcastReceiver).createCanceledIntent(
eq(mContext), eq(mIntent));
+ // spy resources to prevent resource not found issues
+ doReturn(mResources).when(mSlicePurchaseBroadcastReceiver).getResources(eq(mContext));
+
// send ACTION_START_SLICE_PURCHASE_APP
+ doReturn(SlicePurchaseController.ACTION_START_SLICE_PURCHASE_APP).when(mIntent).getAction();
+ doReturn(PHONE_ID).when(mIntent).getIntExtra(
+ eq(SlicePurchaseController.EXTRA_PHONE_ID), anyInt());
+ doReturn(SubscriptionManager.getDefaultDataSubscriptionId()).when(mIntent).getIntExtra(
+ eq(SlicePurchaseController.EXTRA_SUB_ID), anyInt());
+ doReturn(TelephonyManager.PREMIUM_CAPABILITY_PRIORITIZE_LATENCY).when(mIntent).getIntExtra(
+ eq(SlicePurchaseController.EXTRA_PREMIUM_CAPABILITY), anyInt());
+ doReturn(SlicePurchaseController.SLICE_PURCHASE_TEST_FILE).when(mIntent).getStringExtra(
+ eq(SlicePurchaseController.EXTRA_PURCHASE_URL));
+ doReturn(TAG).when(mIntent).getStringExtra(
+ eq(SlicePurchaseController.EXTRA_REQUESTING_APP_NAME));
mSlicePurchaseBroadcastReceiver.onReceive(mContext, mIntent);
-
- // verify network boost notification was shown
- verify(mNotificationManager).notifyAsUser(
- eq(SlicePurchaseBroadcastReceiver.NETWORK_BOOST_NOTIFICATION_TAG),
- eq(TelephonyManager.PREMIUM_CAPABILITY_PRIORITIZE_LATENCY),
- mNotificationCaptor.capture(),
- eq(UserHandle.ALL));
-
- Notification notification = mNotificationCaptor.getValue();
- assertEquals(mContentIntent1, notification.contentIntent);
- assertEquals(mPendingIntent, notification.deleteIntent);
- assertEquals(2, notification.actions.length);
- assertEquals(mCanceledIntent, notification.actions[0].actionIntent);
- assertEquals(mContentIntent2, notification.actions[1].actionIntent);
-
- // verify SlicePurchaseController was notified
- verify(mNotificationShownIntent).send();
}
@Test
public void testNotificationCanceled() {
- // set up intent
+ // send ACTION_NOTIFICATION_CANCELED
doReturn("com.android.phone.slice.action.NOTIFICATION_CANCELED").when(mIntent).getAction();
doReturn(TelephonyManager.PREMIUM_CAPABILITY_PRIORITIZE_LATENCY).when(mIntent).getIntExtra(
eq(SlicePurchaseController.EXTRA_PREMIUM_CAPABILITY), anyInt());
-
- // send ACTION_NOTIFICATION_CANCELED
mSlicePurchaseBroadcastReceiver.onReceive(mContext, mIntent);
// verify notification was canceled
@@ -215,14 +250,14 @@
}
@Test
- public void testNotificationTimeout() {
- // set up intent
+ public void testNotificationTimeout() throws Exception {
+ displayNetworkBoostNotification();
+
+ // send ACTION_SLICE_PURCHASE_APP_RESPONSE_TIMEOUT
doReturn(SlicePurchaseController.ACTION_SLICE_PURCHASE_APP_RESPONSE_TIMEOUT).when(mIntent)
.getAction();
doReturn(TelephonyManager.PREMIUM_CAPABILITY_PRIORITIZE_LATENCY).when(mIntent).getIntExtra(
eq(SlicePurchaseController.EXTRA_PREMIUM_CAPABILITY), anyInt());
-
- // send ACTION_SLICE_PURCHASE_APP_RESPONSE_TIMEOUT
mSlicePurchaseBroadcastReceiver.onReceive(mContext, mIntent);
// verify notification was canceled
@@ -233,27 +268,52 @@
}
@Test
- // TODO: WebView/Activity should not close on timeout.
- // This test should be removed once implementation is fixed.
- public void testActivityTimeout() {
- // create and track activity
- SlicePurchaseBroadcastReceiver.updateSlicePurchaseActivity(
- TelephonyManager.PREMIUM_CAPABILITY_PRIORITIZE_LATENCY, mSlicePurchaseActivity);
+ public void testLocaleChanged() throws Exception {
+ // get previous locale
+ doReturn(mConfiguration).when(mSpiedResources).getConfiguration();
+ Locale before = getLocale();
- // set up intent
- doReturn(SlicePurchaseController.ACTION_SLICE_PURCHASE_APP_RESPONSE_TIMEOUT).when(mIntent)
- .getAction();
- doReturn(TelephonyManager.PREMIUM_CAPABILITY_PRIORITIZE_LATENCY).when(mIntent).getIntExtra(
- eq(SlicePurchaseController.EXTRA_PREMIUM_CAPABILITY), anyInt());
+ // display notification
+ displayNetworkBoostNotification();
+ clearInvocations(mNotificationManager);
+ clearInvocations(mNotificationShownIntent);
- // send ACTION_SLICE_PURCHASE_APP_RESPONSE_TIMEOUT
+ // change current locale from previous value
+ Locale newLocale = Locale.forLanguageTag("en-US");
+ if (before.equals(newLocale)) {
+ newLocale = Locale.forLanguageTag("ko-KR");
+ }
+ doReturn(newLocale).when(mSlicePurchaseBroadcastReceiver).getCurrentLocale();
+
+ // send ACTION_LOCALE_CHANGED
+ doReturn(Intent.ACTION_LOCALE_CHANGED).when(mIntent).getAction();
mSlicePurchaseBroadcastReceiver.onReceive(mContext, mIntent);
- // verify activity was canceled
- verify(mSlicePurchaseActivity).finishAndRemoveTask();
+ // verify notification was updated and SlicePurchaseController was not notified
+ verify(mNotificationManager).cancelAsUser(
+ eq(SlicePurchaseBroadcastReceiver.NETWORK_BOOST_NOTIFICATION_TAG),
+ eq(TelephonyManager.PREMIUM_CAPABILITY_PRIORITIZE_LATENCY),
+ eq(UserHandle.ALL));
+ verify(mNotificationManager).notifyAsUser(
+ eq(SlicePurchaseBroadcastReceiver.NETWORK_BOOST_NOTIFICATION_TAG),
+ eq(TelephonyManager.PREMIUM_CAPABILITY_PRIORITIZE_LATENCY),
+ any(Notification.class),
+ eq(UserHandle.ALL));
+ verify(mNotificationShownIntent, never()).send();
- // untrack activity
- SlicePurchaseBroadcastReceiver.removeSlicePurchaseActivity(
- TelephonyManager.PREMIUM_CAPABILITY_PRIORITIZE_LATENCY);
+ // verify locale was changed successfully
+ doCallRealMethod().when(mSlicePurchaseBroadcastReceiver).getResources(eq(mContext));
+ assertEquals(newLocale, getLocale());
+ }
+
+ private Locale getLocale() {
+ try {
+ mSlicePurchaseBroadcastReceiver.getResources(mContext);
+ fail("getLocale should not have completed successfully.");
+ } catch (NullPointerException expected) { }
+ ArgumentCaptor<Locale> captor = ArgumentCaptor.forClass(Locale.class);
+ verify(mConfiguration).setLocale(captor.capture());
+ clearInvocations(mConfiguration);
+ return captor.getValue();
}
}
diff --git a/packages/CompanionDeviceManager/res/values/strings.xml b/packages/CompanionDeviceManager/res/values/strings.xml
index 3b21541..0764d90 100644
--- a/packages/CompanionDeviceManager/res/values/strings.xml
+++ b/packages/CompanionDeviceManager/res/values/strings.xml
@@ -43,7 +43,7 @@
<string name="profile_name_glasses">glasses</string>
<!-- Description of the privileges the application will get if associated with the companion device of GLASSES profile (type) [CHAR LIMIT=NONE] -->
- <string name="summary_glasses">This app is needed to manage your <xliff:g id="device_name" example="My Glasses">%1$s</xliff:g>. <xliff:g id="app_name" example="Glasses">%2$s</xliff:g> will be allowed to access your Phone, SMS, Contacts, Microphone and Nearby devices permissions.</string>
+ <string name="summary_glasses">This app is needed to manage your <xliff:g id="device_name" example="My Glasses">%1$s</xliff:g>. <xliff:g id="app_name" example="Glasses">%2$s</xliff:g> will be allowed to interact with your notifications and access your Phone, SMS, Contacts, Microphone and Nearby devices permissions.</string>
<!-- Description of the privileges the application will get if associated with the companion device of GLASSES profile for singleDevice(type) [CHAR LIMIT=NONE] -->
<string name="summary_glasses_single_device">The app is needed to manage your <xliff:g id="device_name" example="My Glasses">%1$s</xliff:g>. <xliff:g id="app_name" example="Glasses">%2$s</xliff:g> will be allowed to interact with these permissions:</string>
diff --git a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceActivity.java b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceActivity.java
index 49a63345..8d0a2c0 100644
--- a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceActivity.java
+++ b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceActivity.java
@@ -592,13 +592,13 @@
setupPermissionList();
} else if (deviceProfile.equals(DEVICE_PROFILE_GLASSES)) {
profileName = getString(R.string.profile_name_glasses);
- title = getHtmlFromResources(this, R.string.confirmation_title, appLabel, profileName);
+ title = getHtmlFromResources(this, R.string.confirmation_title, appLabel, deviceName);
summary = getHtmlFromResources(
this, R.string.summary_glasses_single_device, profileName, appLabel);
profileIcon = getIcon(this, R.drawable.ic_glasses);
mPermissionTypes.addAll(Arrays.asList(
- PERMISSION_PHONE, PERMISSION_SMS, PERMISSION_CONTACTS,
+ PERMISSION_NOTIFICATION, PERMISSION_PHONE, PERMISSION_SMS, PERMISSION_CONTACTS,
PERMISSION_MICROPHONE, PERMISSION_NEARBY_DEVICES));
setupPermissionList();
diff --git a/packages/CredentialManager/res/values-af/strings.xml b/packages/CredentialManager/res/values-af/strings.xml
index cbf4542..c42af9c 100644
--- a/packages/CredentialManager/res/values-af/strings.xml
+++ b/packages/CredentialManager/res/values-af/strings.xml
@@ -40,8 +40,7 @@
<string name="other_password_manager" msgid="565790221427004141">"Ander wagwoordbestuurders"</string>
<string name="close_sheet" msgid="1393792015338908262">"Maak sigblad toe"</string>
<string name="accessibility_back_arrow_button" msgid="3233198183497842492">"Gaan terug na die vorige bladsy"</string>
- <!-- no translation found for accessibility_close_button (2953807735590034688) -->
- <skip />
+ <string name="accessibility_close_button" msgid="2953807735590034688">"Maak die Eiebewysbestuurder se handelingvoorstel toe wat onderaan die skerm verskyn"</string>
<string name="get_dialog_title_use_passkey_for" msgid="6236608872708021767">"Gebruik jou gestoorde wagwoordsleutel vir <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
<string name="get_dialog_title_use_sign_in_for" msgid="5283099528915572980">"Gebruik jou gestoorde aanmelding vir <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
<string name="get_dialog_title_choose_sign_in_for" msgid="1361715440877613701">"Kies ’n gestoorde aanmelding vir <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
diff --git a/packages/CredentialManager/res/values-am/strings.xml b/packages/CredentialManager/res/values-am/strings.xml
index e200795..f0705fb 100644
--- a/packages/CredentialManager/res/values-am/strings.xml
+++ b/packages/CredentialManager/res/values-am/strings.xml
@@ -8,21 +8,15 @@
<string name="string_save_to_another_place" msgid="7590325934591079193">"ወደ ሌላ ቦታ ያስቀምጡ"</string>
<string name="string_use_another_device" msgid="8754514926121520445">"ሌላ መሣሪያ ይጠቀሙ"</string>
<string name="string_save_to_another_device" msgid="1959562542075194458">"ወደ ሌላ መሣሪያ ያስቀምጡ"</string>
- <!-- no translation found for passkey_creation_intro_title (4251037543787718844) -->
- <skip />
- <!-- no translation found for passkey_creation_intro_body_password (312712407571126228) -->
- <skip />
- <!-- no translation found for passkey_creation_intro_body_fingerprint (691816235541508203) -->
- <skip />
- <!-- no translation found for passkey_creation_intro_body_device (477121861162321129) -->
- <skip />
+ <string name="passkey_creation_intro_title" msgid="4251037543787718844">"በይለፍ ቃል ይበልጥ ደህንነቱ የተጠበቀ"</string>
+ <string name="passkey_creation_intro_body_password" msgid="312712407571126228">"ውስብስብ የይለፍ ቃላትን መፍጠር ወይም ማስታወስ አያስፈልግም"</string>
+ <string name="passkey_creation_intro_body_fingerprint" msgid="691816235541508203">"ልዩ የይለፍ ቁልፍ ለመፍጠር የእርስዎን የጣት አሻራ፣ ፊት ወይም ማያ ገጽ መቆለፊያ ይጠቀሙ"</string>
+ <string name="passkey_creation_intro_body_device" msgid="477121861162321129">"የይለፍ ቁልፎች የይለፍ ቃል አስተዳዳሪ ላይ ይቀመጣሉ፣ ስለዚህ በሌሎች መሣሪያዎች ላይ መግባት ይችላሉ"</string>
<string name="choose_provider_title" msgid="7245243990139698508">"የት <xliff:g id="CREATETYPES">%1$s</xliff:g> እንደሚሆን ይምረጡ"</string>
- <!-- no translation found for create_your_passkeys (8901224153607590596) -->
- <skip />
+ <string name="create_your_passkeys" msgid="8901224153607590596">"የይለፍ ቁልፎችዎን ይፍጠሩ"</string>
<string name="save_your_password" msgid="6597736507991704307">"የይለፍ ቃልዎን ያስቀምጡ"</string>
<string name="save_your_sign_in_info" msgid="7213978049817076882">"የመግቢያ መረጃዎን ያስቀምጡ"</string>
- <!-- no translation found for choose_provider_body (8045759834416308059) -->
- <skip />
+ <string name="choose_provider_body" msgid="8045759834416308059">"የእርስዎን የይለፍ ቃላት እና የይለፍ ቁልፎች ለማስቀመጥ እና በሚቀጥለው ጊዜ በበለጠ ፍጥነት ለመግባት ነባሪ የሚስጥር ቁልፍ አስተዳዳሪ ያቀናብሩ።"</string>
<string name="choose_create_option_passkey_title" msgid="4146408187146573131">"በ<xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> ውስጥ የይለፍ ቁልፍ ይፈጠር?"</string>
<string name="choose_create_option_password_title" msgid="8812546498357380545">"የይለፍ ቃልዎ ወደ <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> ይቀመጥ?"</string>
<string name="choose_create_option_sign_in_title" msgid="6318246378475961834">"የመግቢያ መረጃዎ ወደ <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> ይቀመጥ?"</string>
@@ -30,17 +24,12 @@
<string name="passkey" msgid="632353688396759522">"የይለፍ ቁልፍ"</string>
<string name="password" msgid="6738570945182936667">"የይለፍ ቃል"</string>
<string name="sign_ins" msgid="4710739369149469208">"መግቢያዎች"</string>
- <!-- no translation found for create_passkey_in_title (2714306562710897785) -->
- <skip />
- <!-- no translation found for save_password_to_title (3450480045270186421) -->
- <skip />
- <!-- no translation found for save_sign_in_to_title (8328143607671760232) -->
- <skip />
- <!-- no translation found for create_passkey_in_other_device_title (6372952459932674632) -->
- <skip />
+ <string name="create_passkey_in_title" msgid="2714306562710897785">"በሚከተለው ውስጥ የይለፍ ቁልፍ ይፈጠሩ"</string>
+ <string name="save_password_to_title" msgid="3450480045270186421">"የይለፍ ቃል አስቀምጥ ወደ"</string>
+ <string name="save_sign_in_to_title" msgid="8328143607671760232">"መግቢያን አስቀምጥ ወደ"</string>
+ <string name="create_passkey_in_other_device_title" msgid="6372952459932674632">"በሌላ መሣሪያ የይለፍ ቁልፍ ይፈጠር?"</string>
<string name="use_provider_for_all_title" msgid="4201020195058980757">"ለሁሉም መግቢያዎችዎ <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>ን ይጠቀሙ?"</string>
- <!-- no translation found for use_provider_for_all_description (6560593199974037820) -->
- <skip />
+ <string name="use_provider_for_all_description" msgid="6560593199974037820">"ይህ የይለፍ ቃል አስተዳዳሪ በቀላሉ እንዲገቡ ለማገዝ የእርስዎን የይለፍ ቃሎች እና የይለፍ ቁልፎች ያከማቻል።"</string>
<string name="set_as_default" msgid="4415328591568654603">"እንደ ነባሪ ያዋቅሩ"</string>
<string name="use_once" msgid="9027366575315399714">"አንዴ ይጠቀሙ"</string>
<string name="more_options_usage_passwords_passkeys" msgid="4794903978126339473">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> የይለፍ ቃሎች፣ <xliff:g id="PASSKEYSNUMBER">%2$s</xliff:g> የይለፍ ቁልፎች"</string>
diff --git a/packages/CredentialManager/res/values-b+sr+Latn/strings.xml b/packages/CredentialManager/res/values-b+sr+Latn/strings.xml
index 414e02a..3680059 100644
--- a/packages/CredentialManager/res/values-b+sr+Latn/strings.xml
+++ b/packages/CredentialManager/res/values-b+sr+Latn/strings.xml
@@ -1,58 +1,58 @@
<?xml version="1.0" encoding="UTF-8"?>
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="app_name" msgid="4539824758261855508">"Menadžer akreditiva"</string>
- <string name="string_cancel" msgid="6369133483981306063">"Otkaži"</string>
- <string name="string_continue" msgid="1346732695941131882">"Nastavi"</string>
- <string name="string_create_in_another_place" msgid="1033635365843437603">"Napravi na drugom mestu"</string>
- <string name="string_save_to_another_place" msgid="7590325934591079193">"Sačuvaj na drugom mestu"</string>
- <string name="string_use_another_device" msgid="8754514926121520445">"Koristi drugi uređaj"</string>
- <string name="string_save_to_another_device" msgid="1959562542075194458">"Sačuvaj na drugi uređaj"</string>
- <string name="passkey_creation_intro_title" msgid="4251037543787718844">"Bezbednije uz pristupne kodove"</string>
- <string name="passkey_creation_intro_body_password" msgid="312712407571126228">"Nema potrebe da pravite ili pamtite složene lozinke"</string>
- <string name="passkey_creation_intro_body_fingerprint" msgid="691816235541508203">"Koristite otisak prsta, lice ili zaključavanje ekrana da biste napravili jedinstveni pristupni kôd"</string>
- <string name="passkey_creation_intro_body_device" msgid="477121861162321129">"Pristupni kodovi se čuvaju u menadžeru lozinki da biste mogli da se prijavljujete na drugim uređajima"</string>
- <string name="choose_provider_title" msgid="7245243990139698508">"Odaberite lokaciju za: <xliff:g id="CREATETYPES">%1$s</xliff:g>"</string>
- <string name="create_your_passkeys" msgid="8901224153607590596">"napravite pristupne kodove"</string>
- <string name="save_your_password" msgid="6597736507991704307">"sačuvajte lozinku"</string>
- <string name="save_your_sign_in_info" msgid="7213978049817076882">"sačuvajte podatke o prijavljivanju"</string>
- <string name="choose_provider_body" msgid="8045759834416308059">"Podesite podrazumevani menadžer lozinki da biste sačuvali lozinke i pristupne kodove i sledeći put se prijavili brže."</string>
- <string name="choose_create_option_passkey_title" msgid="4146408187146573131">"Želite da napravite pristupni kôd kod korisnika <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>?"</string>
- <string name="choose_create_option_password_title" msgid="8812546498357380545">"Želite da sačuvate lozinku kod korisnika <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>?"</string>
- <string name="choose_create_option_sign_in_title" msgid="6318246378475961834">"Želite da sačuvate podatke o prijavljivanju kod korisnika <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>?"</string>
- <string name="choose_create_option_description" msgid="4419171903963100257">"Možete da koristite tip domena <xliff:g id="APPDOMAINNAME">%1$s</xliff:g> <xliff:g id="TYPE">%2$s</xliff:g> na bilo kom uređaju. Čuva se kod korisnika <xliff:g id="PROVIDERINFODISPLAYNAME">%3$s</xliff:g> za: <xliff:g id="CREATEINFODISPLAYNAME">%4$s</xliff:g>"</string>
- <string name="passkey" msgid="632353688396759522">"pristupni kôd"</string>
- <string name="password" msgid="6738570945182936667">"lozinka"</string>
- <string name="sign_ins" msgid="4710739369149469208">"prijavljivanja"</string>
- <string name="create_passkey_in_title" msgid="2714306562710897785">"Napravite pristupni kôd u:"</string>
- <string name="save_password_to_title" msgid="3450480045270186421">"Sačuvajte lozinku na:"</string>
- <string name="save_sign_in_to_title" msgid="8328143607671760232">"Sačuvajte podatke o prijavljivanju na:"</string>
- <string name="create_passkey_in_other_device_title" msgid="6372952459932674632">"Želite da napravite pristupni kôd na drugom uređaju?"</string>
- <string name="use_provider_for_all_title" msgid="4201020195058980757">"Želite da za sva prijavljivanja koristite: <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>?"</string>
- <string name="use_provider_for_all_description" msgid="6560593199974037820">"Ovaj menadžer lozinki će čuvati lozinke i pristupne kodove da biste se lako prijavljivali."</string>
- <string name="set_as_default" msgid="4415328591568654603">"Podesi kao podrazumevano"</string>
- <string name="use_once" msgid="9027366575315399714">"Koristi jednom"</string>
- <string name="more_options_usage_passwords_passkeys" msgid="4794903978126339473">"Lozinki: <xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g>, pristupnih kodova:<xliff:g id="PASSKEYSNUMBER">%2$s</xliff:g>"</string>
- <string name="more_options_usage_passwords" msgid="1632047277723187813">"Lozinki: <xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g>"</string>
- <string name="more_options_usage_passkeys" msgid="5390320437243042237">"Pristupnih kodova: <xliff:g id="PASSKEYSNUMBER">%1$s</xliff:g>"</string>
- <string name="passkey_before_subtitle" msgid="2448119456208647444">"Pristupni kôd"</string>
- <string name="another_device" msgid="5147276802037801217">"Drugi uređaj"</string>
- <string name="other_password_manager" msgid="565790221427004141">"Drugi menadžeri lozinki"</string>
- <string name="close_sheet" msgid="1393792015338908262">"Zatvorite tabelu"</string>
- <string name="accessibility_back_arrow_button" msgid="3233198183497842492">"Vratite se na prethodnu stranicu"</string>
+ <string name="app_name" msgid="4539824758261855508">"Менаџер акредитива"</string>
+ <string name="string_cancel" msgid="6369133483981306063">"Откажи"</string>
+ <string name="string_continue" msgid="1346732695941131882">"Настави"</string>
+ <string name="string_create_in_another_place" msgid="1033635365843437603">"Направи на другом месту"</string>
+ <string name="string_save_to_another_place" msgid="7590325934591079193">"Сачувај на другом месту"</string>
+ <string name="string_use_another_device" msgid="8754514926121520445">"Користи други уређај"</string>
+ <string name="string_save_to_another_device" msgid="1959562542075194458">"Сачувај на други уређај"</string>
+ <string name="passkey_creation_intro_title" msgid="4251037543787718844">"Безбедније уз приступне кодове"</string>
+ <string name="passkey_creation_intro_body_password" msgid="312712407571126228">"Нема потребе да правите или памтите сложене лозинке"</string>
+ <string name="passkey_creation_intro_body_fingerprint" msgid="691816235541508203">"Користите отисак прста, лице или закључавање екрана да бисте направили јединствени приступни кôд"</string>
+ <string name="passkey_creation_intro_body_device" msgid="477121861162321129">"Приступни кодови се чувају у менаџеру лозинки да бисте могли да се пријављујете на другим уређајима"</string>
+ <string name="choose_provider_title" msgid="7245243990139698508">"Одаберите локацију за: <xliff:g id="CREATETYPES">%1$s</xliff:g>"</string>
+ <string name="create_your_passkeys" msgid="8901224153607590596">"направите приступне кодове"</string>
+ <string name="save_your_password" msgid="6597736507991704307">"сачувајте лозинку"</string>
+ <string name="save_your_sign_in_info" msgid="7213978049817076882">"сачувајте податке о пријављивању"</string>
+ <string name="choose_provider_body" msgid="8045759834416308059">"Подесите подразумевани менаџер лозинки да бисте сачували лозинке и приступне кодове и следећи пут се пријавили брже."</string>
+ <string name="choose_create_option_passkey_title" msgid="4146408187146573131">"Желите да направите приступни кôд код корисника <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>?"</string>
+ <string name="choose_create_option_password_title" msgid="8812546498357380545">"Желите да сачувате лозинку код корисника <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>?"</string>
+ <string name="choose_create_option_sign_in_title" msgid="6318246378475961834">"Желите да сачувате податке о пријављивању код корисника <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>?"</string>
+ <string name="choose_create_option_description" msgid="4419171903963100257">"Можете да користите тип домена <xliff:g id="APPDOMAINNAME">%1$s</xliff:g> <xliff:g id="TYPE">%2$s</xliff:g> на било ком уређају. Чува се код корисника <xliff:g id="PROVIDERINFODISPLAYNAME">%3$s</xliff:g> за: <xliff:g id="CREATEINFODISPLAYNAME">%4$s</xliff:g>"</string>
+ <string name="passkey" msgid="632353688396759522">"приступни кôд"</string>
+ <string name="password" msgid="6738570945182936667">"лозинка"</string>
+ <string name="sign_ins" msgid="4710739369149469208">"пријављивања"</string>
+ <string name="create_passkey_in_title" msgid="2714306562710897785">"Направите приступни кôд у:"</string>
+ <string name="save_password_to_title" msgid="3450480045270186421">"Сачувајте лозинку на:"</string>
+ <string name="save_sign_in_to_title" msgid="8328143607671760232">"Сачувајте податке о пријављивању на:"</string>
+ <string name="create_passkey_in_other_device_title" msgid="6372952459932674632">"Желите да направите приступни кôд на другом уређају?"</string>
+ <string name="use_provider_for_all_title" msgid="4201020195058980757">"Желите да за сва пријављивања користите: <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>?"</string>
+ <string name="use_provider_for_all_description" msgid="6560593199974037820">"Овај менаџер лозинки ће чувати лозинке и приступне кодове да бисте се лако пријављивали."</string>
+ <string name="set_as_default" msgid="4415328591568654603">"Подеси као подразумевано"</string>
+ <string name="use_once" msgid="9027366575315399714">"Користи једном"</string>
+ <string name="more_options_usage_passwords_passkeys" msgid="4794903978126339473">"Лозинки: <xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g>, приступних кодова:<xliff:g id="PASSKEYSNUMBER">%2$s</xliff:g>"</string>
+ <string name="more_options_usage_passwords" msgid="1632047277723187813">"Лозинки: <xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g>"</string>
+ <string name="more_options_usage_passkeys" msgid="5390320437243042237">"Приступних кодова: <xliff:g id="PASSKEYSNUMBER">%1$s</xliff:g>"</string>
+ <string name="passkey_before_subtitle" msgid="2448119456208647444">"Приступни кôд"</string>
+ <string name="another_device" msgid="5147276802037801217">"Други уређај"</string>
+ <string name="other_password_manager" msgid="565790221427004141">"Други менаџери лозинки"</string>
+ <string name="close_sheet" msgid="1393792015338908262">"Затворите табелу"</string>
+ <string name="accessibility_back_arrow_button" msgid="3233198183497842492">"Вратите се на претходну страницу"</string>
<!-- no translation found for accessibility_close_button (2953807735590034688) -->
<skip />
- <string name="get_dialog_title_use_passkey_for" msgid="6236608872708021767">"Želite da koristite sačuvani pristupni kôd za: <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
- <string name="get_dialog_title_use_sign_in_for" msgid="5283099528915572980">"Želite da koristite sačuvane podatke za prijavljivanje za: <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
- <string name="get_dialog_title_choose_sign_in_for" msgid="1361715440877613701">"Odaberite sačuvano prijavljivanje za: <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
- <string name="get_dialog_use_saved_passkey_for" msgid="4618100798664888512">"Prijavite se na drugi način"</string>
- <string name="get_dialog_button_label_no_thanks" msgid="8114363019023838533">"Ne, hvala"</string>
- <string name="get_dialog_button_label_continue" msgid="6446201694794283870">"Nastavi"</string>
- <string name="get_dialog_title_sign_in_options" msgid="2092876443114893618">"Opcije za prijavljivanje"</string>
- <string name="get_dialog_heading_for_username" msgid="3456868514554204776">"Za: <xliff:g id="USERNAME">%1$s</xliff:g>"</string>
- <string name="get_dialog_heading_locked_password_managers" msgid="8911514851762862180">"Menadžeri zaključanih lozinki"</string>
- <string name="locked_credential_entry_label_subtext" msgid="9213450912991988691">"Dodirnite da biste otključali"</string>
- <string name="get_dialog_heading_manage_sign_ins" msgid="3522556476480676782">"Upravljajte prijavljivanjima"</string>
- <string name="get_dialog_heading_from_another_device" msgid="1166697017046724072">"Sa drugog uređaja"</string>
- <string name="get_dialog_option_headline_use_a_different_device" msgid="8201578814988047549">"Koristi drugi uređaj"</string>
+ <string name="get_dialog_title_use_passkey_for" msgid="6236608872708021767">"Желите да користите сачувани приступни кôд за: <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
+ <string name="get_dialog_title_use_sign_in_for" msgid="5283099528915572980">"Желите да користите сачуване податке за пријављивање за: <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
+ <string name="get_dialog_title_choose_sign_in_for" msgid="1361715440877613701">"Одаберите сачувано пријављивање за: <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+ <string name="get_dialog_use_saved_passkey_for" msgid="4618100798664888512">"Пријавите се на други начин"</string>
+ <string name="get_dialog_button_label_no_thanks" msgid="8114363019023838533">"Не, хвала"</string>
+ <string name="get_dialog_button_label_continue" msgid="6446201694794283870">"Настави"</string>
+ <string name="get_dialog_title_sign_in_options" msgid="2092876443114893618">"Опције за пријављивање"</string>
+ <string name="get_dialog_heading_for_username" msgid="3456868514554204776">"За: <xliff:g id="USERNAME">%1$s</xliff:g>"</string>
+ <string name="get_dialog_heading_locked_password_managers" msgid="8911514851762862180">"Менаџери закључаних лозинки"</string>
+ <string name="locked_credential_entry_label_subtext" msgid="9213450912991988691">"Додирните да бисте откључали"</string>
+ <string name="get_dialog_heading_manage_sign_ins" msgid="3522556476480676782">"Управљајте пријављивањима"</string>
+ <string name="get_dialog_heading_from_another_device" msgid="1166697017046724072">"Са другог уређаја"</string>
+ <string name="get_dialog_option_headline_use_a_different_device" msgid="8201578814988047549">"Користи други уређај"</string>
</resources>
diff --git a/packages/CredentialManager/res/values-bn/strings.xml b/packages/CredentialManager/res/values-bn/strings.xml
index f6654fc..6db94ba 100644
--- a/packages/CredentialManager/res/values-bn/strings.xml
+++ b/packages/CredentialManager/res/values-bn/strings.xml
@@ -40,8 +40,7 @@
<string name="other_password_manager" msgid="565790221427004141">"অন্যান্য Password Manager"</string>
<string name="close_sheet" msgid="1393792015338908262">"শিট বন্ধ করুন"</string>
<string name="accessibility_back_arrow_button" msgid="3233198183497842492">"আগের পৃষ্ঠায় ফিরে যান"</string>
- <!-- no translation found for accessibility_close_button (2953807735590034688) -->
- <skip />
+ <string name="accessibility_close_button" msgid="2953807735590034688">"স্ক্রিনের নিচে দেখানো Credential Manager অ্যাকশন সংক্রান্ত সাজেশন বন্ধ করুন"</string>
<string name="get_dialog_title_use_passkey_for" msgid="6236608872708021767">"<xliff:g id="APP_NAME">%1$s</xliff:g>-এর জন্য আপনার সেভ করা পাসকী ব্যবহার করবেন?"</string>
<string name="get_dialog_title_use_sign_in_for" msgid="5283099528915572980">"<xliff:g id="APP_NAME">%1$s</xliff:g>-এর জন্য আপনার সেভ করা সাইন-ইন সম্পর্কিত ক্রেডেনশিয়াল ব্যবহার করবেন?"</string>
<string name="get_dialog_title_choose_sign_in_for" msgid="1361715440877613701">"<xliff:g id="APP_NAME">%1$s</xliff:g>-এর জন্য সাইন-ইন করা সম্পর্কিত ক্রেডেনশিয়াল বেছে নিন"</string>
diff --git a/packages/CredentialManager/res/values-da/strings.xml b/packages/CredentialManager/res/values-da/strings.xml
index 4a1f7de..14d8dc0 100644
--- a/packages/CredentialManager/res/values-da/strings.xml
+++ b/packages/CredentialManager/res/values-da/strings.xml
@@ -40,8 +40,7 @@
<string name="other_password_manager" msgid="565790221427004141">"Andre adgangskodeadministratorer"</string>
<string name="close_sheet" msgid="1393792015338908262">"Luk arket"</string>
<string name="accessibility_back_arrow_button" msgid="3233198183497842492">"Gå tilbage til den forrige side"</string>
- <!-- no translation found for accessibility_close_button (2953807735590034688) -->
- <skip />
+ <string name="accessibility_close_button" msgid="2953807735590034688">"Luk handlingsforslaget for Loginstyring, som vises nederst på skærmen"</string>
<string name="get_dialog_title_use_passkey_for" msgid="6236608872708021767">"Vil du bruge din gemte adgangsnøgle til <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
<string name="get_dialog_title_use_sign_in_for" msgid="5283099528915572980">"Vil du bruge din gemte loginmetode til <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
<string name="get_dialog_title_choose_sign_in_for" msgid="1361715440877613701">"Vælg en gemt loginmetode til <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
diff --git a/packages/CredentialManager/res/values-es-rUS/strings.xml b/packages/CredentialManager/res/values-es-rUS/strings.xml
index 73c7eab..1a93a29 100644
--- a/packages/CredentialManager/res/values-es-rUS/strings.xml
+++ b/packages/CredentialManager/res/values-es-rUS/strings.xml
@@ -40,8 +40,7 @@
<string name="other_password_manager" msgid="565790221427004141">"Otros administradores de contraseñas"</string>
<string name="close_sheet" msgid="1393792015338908262">"Cerrar hoja"</string>
<string name="accessibility_back_arrow_button" msgid="3233198183497842492">"Volver a la página anterior"</string>
- <!-- no translation found for accessibility_close_button (2953807735590034688) -->
- <skip />
+ <string name="accessibility_close_button" msgid="2953807735590034688">"Cierra la sugerencia de acción del Administrador de credenciales que aparece en la parte inferior de la pantalla"</string>
<string name="get_dialog_title_use_passkey_for" msgid="6236608872708021767">"¿Quieres usar tu llave de acceso guardada para <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
<string name="get_dialog_title_use_sign_in_for" msgid="5283099528915572980">"¿Quieres usar tu acceso guardado para <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
<string name="get_dialog_title_choose_sign_in_for" msgid="1361715440877613701">"Elige un acceso guardado para <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
diff --git a/packages/CredentialManager/res/values-es/strings.xml b/packages/CredentialManager/res/values-es/strings.xml
index b08839a..1dd8aa7 100644
--- a/packages/CredentialManager/res/values-es/strings.xml
+++ b/packages/CredentialManager/res/values-es/strings.xml
@@ -40,8 +40,7 @@
<string name="other_password_manager" msgid="565790221427004141">"Otros gestores de contraseñas"</string>
<string name="close_sheet" msgid="1393792015338908262">"Cerrar hoja"</string>
<string name="accessibility_back_arrow_button" msgid="3233198183497842492">"Volver a la página anterior"</string>
- <!-- no translation found for accessibility_close_button (2953807735590034688) -->
- <skip />
+ <string name="accessibility_close_button" msgid="2953807735590034688">"Cierra la sugerencia de acción del Gestor de credenciales de la parte inferior de la pantalla"</string>
<string name="get_dialog_title_use_passkey_for" msgid="6236608872708021767">"¿Usar la llave de acceso guardada para <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
<string name="get_dialog_title_use_sign_in_for" msgid="5283099528915572980">"¿Usar el inicio de sesión guardado para <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
<string name="get_dialog_title_choose_sign_in_for" msgid="1361715440877613701">"Elige un inicio de sesión guardado para <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
diff --git a/packages/CredentialManager/res/values-fr-rCA/strings.xml b/packages/CredentialManager/res/values-fr-rCA/strings.xml
index de2ed5f..4350a47 100644
--- a/packages/CredentialManager/res/values-fr-rCA/strings.xml
+++ b/packages/CredentialManager/res/values-fr-rCA/strings.xml
@@ -40,8 +40,7 @@
<string name="other_password_manager" msgid="565790221427004141">"Autres gestionnaires de mots de passe"</string>
<string name="close_sheet" msgid="1393792015338908262">"Fermer la feuille"</string>
<string name="accessibility_back_arrow_button" msgid="3233198183497842492">"Retourner à la page précédente"</string>
- <!-- no translation found for accessibility_close_button (2953807735590034688) -->
- <skip />
+ <string name="accessibility_close_button" msgid="2953807735590034688">"Fermez l\'action suggérée par le gestionnaire d\'authentifiants qui est affichée au bas de l\'écran"</string>
<string name="get_dialog_title_use_passkey_for" msgid="6236608872708021767">"Utiliser votre clé d\'accès enregistrée pour <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
<string name="get_dialog_title_use_sign_in_for" msgid="5283099528915572980">"Utiliser votre connexion enregistrée pour <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
<string name="get_dialog_title_choose_sign_in_for" msgid="1361715440877613701">"Choisir une connexion enregistrée pour <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
diff --git a/packages/CredentialManager/res/values-hu/strings.xml b/packages/CredentialManager/res/values-hu/strings.xml
index 85d3137..4846eb9 100644
--- a/packages/CredentialManager/res/values-hu/strings.xml
+++ b/packages/CredentialManager/res/values-hu/strings.xml
@@ -40,8 +40,7 @@
<string name="other_password_manager" msgid="565790221427004141">"Egyéb jelszókezelők"</string>
<string name="close_sheet" msgid="1393792015338908262">"Munkalap bezárása"</string>
<string name="accessibility_back_arrow_button" msgid="3233198183497842492">"Vissza az előző oldalra"</string>
- <!-- no translation found for accessibility_close_button (2953807735590034688) -->
- <skip />
+ <string name="accessibility_close_button" msgid="2953807735590034688">"A Tanúsítványkezelő képernyő alján megjelenő műveletjavaslat bezárása"</string>
<string name="get_dialog_title_use_passkey_for" msgid="6236608872708021767">"Szeretné a(z) <xliff:g id="APP_NAME">%1$s</xliff:g> alkalmazáshoz mentett azonosítókulcsot használni?"</string>
<string name="get_dialog_title_use_sign_in_for" msgid="5283099528915572980">"Szeretné a(z) <xliff:g id="APP_NAME">%1$s</xliff:g> alkalmazáshoz mentett bejelentkezési adatait használni?"</string>
<string name="get_dialog_title_choose_sign_in_for" msgid="1361715440877613701">"Mentett bejelentkezési adatok választása a következő számára: <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
diff --git a/packages/CredentialManager/res/values-hy/strings.xml b/packages/CredentialManager/res/values-hy/strings.xml
index c51e15e..57cd19b 100644
--- a/packages/CredentialManager/res/values-hy/strings.xml
+++ b/packages/CredentialManager/res/values-hy/strings.xml
@@ -40,8 +40,7 @@
<string name="other_password_manager" msgid="565790221427004141">"Գաղտնաբառերի այլ կառավարիչներ"</string>
<string name="close_sheet" msgid="1393792015338908262">"Փակել թերթը"</string>
<string name="accessibility_back_arrow_button" msgid="3233198183497842492">"Անցնել նախորդ էջ"</string>
- <!-- no translation found for accessibility_close_button (2953807735590034688) -->
- <skip />
+ <string name="accessibility_close_button" msgid="2953807735590034688">"Փակել Մուտքի տվյալների կառավարչի հուշումը, որը ցուցադրվում է էկրանի ներքևի հատվածում"</string>
<string name="get_dialog_title_use_passkey_for" msgid="6236608872708021767">"Օգտագործե՞լ պահված անցաբառը <xliff:g id="APP_NAME">%1$s</xliff:g> հավելվածի համար"</string>
<string name="get_dialog_title_use_sign_in_for" msgid="5283099528915572980">"Օգտագործե՞լ մուտքի պահված տվյալները <xliff:g id="APP_NAME">%1$s</xliff:g> հավելվածի համար"</string>
<string name="get_dialog_title_choose_sign_in_for" msgid="1361715440877613701">"Ընտրեք մուտքի պահված տվյալներ <xliff:g id="APP_NAME">%1$s</xliff:g> հավելվածի համար"</string>
diff --git a/packages/CredentialManager/res/values-in/strings.xml b/packages/CredentialManager/res/values-in/strings.xml
index fc20ad88..aa09da2 100644
--- a/packages/CredentialManager/res/values-in/strings.xml
+++ b/packages/CredentialManager/res/values-in/strings.xml
@@ -40,8 +40,7 @@
<string name="other_password_manager" msgid="565790221427004141">"Pengelola sandi lainnya"</string>
<string name="close_sheet" msgid="1393792015338908262">"Tutup sheet"</string>
<string name="accessibility_back_arrow_button" msgid="3233198183497842492">"Kembali ke halaman sebelumnya"</string>
- <!-- no translation found for accessibility_close_button (2953807735590034688) -->
- <skip />
+ <string name="accessibility_close_button" msgid="2953807735590034688">"Tutup saran tindakan Pengelola Kredensial yang muncul di bagian bawah layar"</string>
<string name="get_dialog_title_use_passkey_for" msgid="6236608872708021767">"Gunakan kunci sandi tersimpan untuk <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
<string name="get_dialog_title_use_sign_in_for" msgid="5283099528915572980">"Gunakan info login tersimpan untuk <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
<string name="get_dialog_title_choose_sign_in_for" msgid="1361715440877613701">"Pilih info login tersimpan untuk <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
diff --git a/packages/CredentialManager/res/values-ja/strings.xml b/packages/CredentialManager/res/values-ja/strings.xml
index 9299281..5416d51 100644
--- a/packages/CredentialManager/res/values-ja/strings.xml
+++ b/packages/CredentialManager/res/values-ja/strings.xml
@@ -40,8 +40,7 @@
<string name="other_password_manager" msgid="565790221427004141">"他のパスワード マネージャー"</string>
<string name="close_sheet" msgid="1393792015338908262">"シートを閉じます"</string>
<string name="accessibility_back_arrow_button" msgid="3233198183497842492">"前のページに戻ります"</string>
- <!-- no translation found for accessibility_close_button (2953807735590034688) -->
- <skip />
+ <string name="accessibility_close_button" msgid="2953807735590034688">"画面下に表示されている認証情報マネージャーのおすすめの操作を閉じます"</string>
<string name="get_dialog_title_use_passkey_for" msgid="6236608872708021767">"<xliff:g id="APP_NAME">%1$s</xliff:g> の保存したパスキーを使用しますか?"</string>
<string name="get_dialog_title_use_sign_in_for" msgid="5283099528915572980">"<xliff:g id="APP_NAME">%1$s</xliff:g> の保存したログイン情報を使用しますか?"</string>
<string name="get_dialog_title_choose_sign_in_for" msgid="1361715440877613701">"<xliff:g id="APP_NAME">%1$s</xliff:g> の保存したログイン情報の選択"</string>
diff --git a/packages/CredentialManager/res/values-ka/strings.xml b/packages/CredentialManager/res/values-ka/strings.xml
index c860590..b2d051a 100644
--- a/packages/CredentialManager/res/values-ka/strings.xml
+++ b/packages/CredentialManager/res/values-ka/strings.xml
@@ -40,8 +40,7 @@
<string name="other_password_manager" msgid="565790221427004141">"პაროლების სხვა მმართველები"</string>
<string name="close_sheet" msgid="1393792015338908262">"ფურცლის დახურვა"</string>
<string name="accessibility_back_arrow_button" msgid="3233198183497842492">"წინა გვერდზე დაბრუნება"</string>
- <!-- no translation found for accessibility_close_button (2953807735590034688) -->
- <skip />
+ <string name="accessibility_close_button" msgid="2953807735590034688">"დახურეთ ავტორიზაციის მონაცემების მმართველის მოქმედებების შემოთავაზებები, რომლებიც ეკრანის ქვემოთ ჩნდება"</string>
<string name="get_dialog_title_use_passkey_for" msgid="6236608872708021767">"გსურთ თქვენი დამახსოვრებული წვდომის გასაღების გამოყენება აპისთვის: <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
<string name="get_dialog_title_use_sign_in_for" msgid="5283099528915572980">"გსურთ თქვენი დამახსოვრებული სისტემაში შესვლის მონაცემების გამოყენება აპისთვის: <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
<string name="get_dialog_title_choose_sign_in_for" msgid="1361715440877613701">"აირჩიეთ სისტემაში შესვლის ინფორმაცია აპისთვის: <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
diff --git a/packages/CredentialManager/res/values-ko/strings.xml b/packages/CredentialManager/res/values-ko/strings.xml
index 7b779b9..d6089ce 100644
--- a/packages/CredentialManager/res/values-ko/strings.xml
+++ b/packages/CredentialManager/res/values-ko/strings.xml
@@ -40,8 +40,7 @@
<string name="other_password_manager" msgid="565790221427004141">"기타 비밀번호 관리자"</string>
<string name="close_sheet" msgid="1393792015338908262">"시트 닫기"</string>
<string name="accessibility_back_arrow_button" msgid="3233198183497842492">"이전 페이지로 돌아가기"</string>
- <!-- no translation found for accessibility_close_button (2953807735590034688) -->
- <skip />
+ <string name="accessibility_close_button" msgid="2953807735590034688">"화면 하단에 표시되는 인증 관리자 작업 제안 닫기"</string>
<string name="get_dialog_title_use_passkey_for" msgid="6236608872708021767">"<xliff:g id="APP_NAME">%1$s</xliff:g> 앱용 저장된 패스키를 사용하시겠습니까?"</string>
<string name="get_dialog_title_use_sign_in_for" msgid="5283099528915572980">"<xliff:g id="APP_NAME">%1$s</xliff:g> 앱용 저장된 로그인 정보를 사용하시겠습니까?"</string>
<string name="get_dialog_title_choose_sign_in_for" msgid="1361715440877613701">"<xliff:g id="APP_NAME">%1$s</xliff:g> 앱용 저장된 로그인 정보 선택"</string>
diff --git a/packages/CredentialManager/res/values-lo/strings.xml b/packages/CredentialManager/res/values-lo/strings.xml
index 5d657e3..fd3a09c 100644
--- a/packages/CredentialManager/res/values-lo/strings.xml
+++ b/packages/CredentialManager/res/values-lo/strings.xml
@@ -40,8 +40,7 @@
<string name="other_password_manager" msgid="565790221427004141">"ຕົວຈັດການລະຫັດຜ່ານອື່ນໆ"</string>
<string name="close_sheet" msgid="1393792015338908262">"ປິດຊີດ"</string>
<string name="accessibility_back_arrow_button" msgid="3233198183497842492">"ກັບຄືນໄປຫາໜ້າກ່ອນໜ້ານີ້"</string>
- <!-- no translation found for accessibility_close_button (2953807735590034688) -->
- <skip />
+ <string name="accessibility_close_button" msgid="2953807735590034688">"ປິດການແນະນຳການດຳເນີນການຂອງຕົວຈັດການຂໍ້ມູນການເຂົ້າສູ່ລະບົບເຊິ່ງປາກົດຢູ່ລຸ່ມສຸດຂອງໜ້າຈໍ"</string>
<string name="get_dialog_title_use_passkey_for" msgid="6236608872708021767">"ໃຊ້ກະແຈຜ່ານທີ່ບັນທຶກໄວ້ຂອງທ່ານສຳລັບ <xliff:g id="APP_NAME">%1$s</xliff:g> ບໍ?"</string>
<string name="get_dialog_title_use_sign_in_for" msgid="5283099528915572980">"ໃຊ້ການເຂົ້າສູ່ລະບົບທີ່ບັນທຶກໄວ້ຂອງທ່ານສຳລັບ <xliff:g id="APP_NAME">%1$s</xliff:g> ບໍ?"</string>
<string name="get_dialog_title_choose_sign_in_for" msgid="1361715440877613701">"ເລືອກການເຂົ້າສູ່ລະບົບທີ່ບັນທຶກໄວ້ສຳລັບ <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
diff --git a/packages/CredentialManager/res/values-lt/strings.xml b/packages/CredentialManager/res/values-lt/strings.xml
index 1b857d7..78a2577 100644
--- a/packages/CredentialManager/res/values-lt/strings.xml
+++ b/packages/CredentialManager/res/values-lt/strings.xml
@@ -40,8 +40,7 @@
<string name="other_password_manager" msgid="565790221427004141">"Kitos slaptažodžių tvarkyklės"</string>
<string name="close_sheet" msgid="1393792015338908262">"Uždaryti lapą"</string>
<string name="accessibility_back_arrow_button" msgid="3233198183497842492">"Grįžti į ankstesnį puslapį"</string>
- <!-- no translation found for accessibility_close_button (2953807735590034688) -->
- <skip />
+ <string name="accessibility_close_button" msgid="2953807735590034688">"Uždaryti prisijungimo duomenų tvarkytuvės veiksmo pasiūlymą, rodomą ekrano apačioje"</string>
<string name="get_dialog_title_use_passkey_for" msgid="6236608872708021767">"Naudoti išsaugotą „passkey“ programai „<xliff:g id="APP_NAME">%1$s</xliff:g>“?"</string>
<string name="get_dialog_title_use_sign_in_for" msgid="5283099528915572980">"Naudoti išsaugotą prisijungimo informaciją programai „<xliff:g id="APP_NAME">%1$s</xliff:g>“?"</string>
<string name="get_dialog_title_choose_sign_in_for" msgid="1361715440877613701">"Pasirinkite išsaugotą prisijungimo informaciją programai „<xliff:g id="APP_NAME">%1$s</xliff:g>“"</string>
diff --git a/packages/CredentialManager/res/values-mk/strings.xml b/packages/CredentialManager/res/values-mk/strings.xml
index 063c497..1fc19f9 100644
--- a/packages/CredentialManager/res/values-mk/strings.xml
+++ b/packages/CredentialManager/res/values-mk/strings.xml
@@ -40,8 +40,7 @@
<string name="other_password_manager" msgid="565790221427004141">"Други управници со лозинки"</string>
<string name="close_sheet" msgid="1393792015338908262">"Затворете го листот"</string>
<string name="accessibility_back_arrow_button" msgid="3233198183497842492">"Врати се на претходната страница"</string>
- <!-- no translation found for accessibility_close_button (2953807735590034688) -->
- <skip />
+ <string name="accessibility_close_button" msgid="2953807735590034688">"Затворете го предложеното дејство за „Управникот со акредитиви“ што се појавува најдолу на екранот"</string>
<string name="get_dialog_title_use_passkey_for" msgid="6236608872708021767">"Да се користи вашиот зачуван криптографски клуч за <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
<string name="get_dialog_title_use_sign_in_for" msgid="5283099528915572980">"Да се користи вашето зачувано најавување за <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
<string name="get_dialog_title_choose_sign_in_for" msgid="1361715440877613701">"Изберете зачувано најавување за <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
diff --git a/packages/CredentialManager/res/values-mn/strings.xml b/packages/CredentialManager/res/values-mn/strings.xml
index 96f3384..9ae8f93 100644
--- a/packages/CredentialManager/res/values-mn/strings.xml
+++ b/packages/CredentialManager/res/values-mn/strings.xml
@@ -40,8 +40,7 @@
<string name="other_password_manager" msgid="565790221427004141">"Нууц үгний бусад менежер"</string>
<string name="close_sheet" msgid="1393792015338908262">"Хүснэгтийг хаах"</string>
<string name="accessibility_back_arrow_button" msgid="3233198183497842492">"Өмнөх хуудас руу буцах"</string>
- <!-- no translation found for accessibility_close_button (2953807735590034688) -->
- <skip />
+ <string name="accessibility_close_button" msgid="2953807735590034688">"Дэлгэцийн доод талд гарч ирэх Мандат үнэмлэхийн менежерийн үйлдлийн зөвлөмжийг хаана"</string>
<string name="get_dialog_title_use_passkey_for" msgid="6236608872708021767">"<xliff:g id="APP_NAME">%1$s</xliff:g>-д өөрийн хадгалсан passkey-г ашиглах уу?"</string>
<string name="get_dialog_title_use_sign_in_for" msgid="5283099528915572980">"<xliff:g id="APP_NAME">%1$s</xliff:g>-д хадгалсан нэвтрэх мэдээллээ ашиглах уу?"</string>
<string name="get_dialog_title_choose_sign_in_for" msgid="1361715440877613701">"<xliff:g id="APP_NAME">%1$s</xliff:g>-д зориулж хадгалсан нэвтрэх мэдээллийг сонгоно уу"</string>
diff --git a/packages/CredentialManager/res/values-mr/strings.xml b/packages/CredentialManager/res/values-mr/strings.xml
index 4ebbf9f..3cff998 100644
--- a/packages/CredentialManager/res/values-mr/strings.xml
+++ b/packages/CredentialManager/res/values-mr/strings.xml
@@ -40,8 +40,7 @@
<string name="other_password_manager" msgid="565790221427004141">"इतर पासवर्ड व्यवस्थापक"</string>
<string name="close_sheet" msgid="1393792015338908262">"शीट बंद करा"</string>
<string name="accessibility_back_arrow_button" msgid="3233198183497842492">"मागील पेजवर परत जा"</string>
- <!-- no translation found for accessibility_close_button (2953807735590034688) -->
- <skip />
+ <string name="accessibility_close_button" msgid="2953807735590034688">"स्क्रीनच्या तळाशी दिसणाऱ्या क्रेडेंशियल व्यवस्थापक कृती सूचना बंद करा"</string>
<string name="get_dialog_title_use_passkey_for" msgid="6236608872708021767">"<xliff:g id="APP_NAME">%1$s</xliff:g> साठी तुमची सेव्ह केलेली पासकी वापरायची का?"</string>
<string name="get_dialog_title_use_sign_in_for" msgid="5283099528915572980">"<xliff:g id="APP_NAME">%1$s</xliff:g> साठी तुमचे सेव्ह केलेले साइन-इन वापरायचे का?"</string>
<string name="get_dialog_title_choose_sign_in_for" msgid="1361715440877613701">"<xliff:g id="APP_NAME">%1$s</xliff:g> साठी सेव्ह केलेले साइन-इन निवडा"</string>
diff --git a/packages/CredentialManager/res/values-ms/strings.xml b/packages/CredentialManager/res/values-ms/strings.xml
index 205a677..2a1fbed 100644
--- a/packages/CredentialManager/res/values-ms/strings.xml
+++ b/packages/CredentialManager/res/values-ms/strings.xml
@@ -40,8 +40,7 @@
<string name="other_password_manager" msgid="565790221427004141">"Password Manager lain"</string>
<string name="close_sheet" msgid="1393792015338908262">"Tutup helaian"</string>
<string name="accessibility_back_arrow_button" msgid="3233198183497842492">"Kembali ke halaman sebelumnya"</string>
- <!-- no translation found for accessibility_close_button (2953807735590034688) -->
- <skip />
+ <string name="accessibility_close_button" msgid="2953807735590034688">"Tutup cadangan tindakan Pengurus Bukti Kelayakan yang muncul di bahagian bawah skrin"</string>
<string name="get_dialog_title_use_passkey_for" msgid="6236608872708021767">"Gunakan kunci laluan anda yang telah disimpan untuk <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
<string name="get_dialog_title_use_sign_in_for" msgid="5283099528915572980">"Gunakan maklumat log masuk anda yang telah disimpan untuk <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
<string name="get_dialog_title_choose_sign_in_for" msgid="1361715440877613701">"Pilih log masuk yang telah disimpan untuk <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
diff --git a/packages/CredentialManager/res/values-my/strings.xml b/packages/CredentialManager/res/values-my/strings.xml
index 5547211d..70ad36a 100644
--- a/packages/CredentialManager/res/values-my/strings.xml
+++ b/packages/CredentialManager/res/values-my/strings.xml
@@ -40,8 +40,7 @@
<string name="other_password_manager" msgid="565790221427004141">"အခြားစကားဝှက်မန်နေဂျာများ"</string>
<string name="close_sheet" msgid="1393792015338908262">"စာမျက်နှာ ပိတ်ရန်"</string>
<string name="accessibility_back_arrow_button" msgid="3233198183497842492">"ယခင်စာမျက်နှာကို ပြန်သွားပါ"</string>
- <!-- no translation found for accessibility_close_button (2953807735590034688) -->
- <skip />
+ <string name="accessibility_close_button" msgid="2953807735590034688">"ဖန်သားပြင်အောက်ခြေတွင် ပြထားသော ‘အထောက်အထားမန်နေဂျာ’ လုပ်ဆောင်မှု အကြံပြုချက်ကို ပိတ်နိုင်သည်"</string>
<string name="get_dialog_title_use_passkey_for" msgid="6236608872708021767">"<xliff:g id="APP_NAME">%1$s</xliff:g> အတွက် သိမ်းထားသောလျှို့ဝှက်ကီး သုံးမလား။"</string>
<string name="get_dialog_title_use_sign_in_for" msgid="5283099528915572980">"<xliff:g id="APP_NAME">%1$s</xliff:g> အတွက် သိမ်းထားသောလက်မှတ်ထိုးဝင်မှု သုံးမလား။"</string>
<string name="get_dialog_title_choose_sign_in_for" msgid="1361715440877613701">"<xliff:g id="APP_NAME">%1$s</xliff:g> အတွက် သိမ်းထားသော လက်မှတ်ထိုးဝင်မှုကို ရွေးပါ"</string>
diff --git a/packages/CredentialManager/res/values-nb/strings.xml b/packages/CredentialManager/res/values-nb/strings.xml
index 3540874..1d82858 100644
--- a/packages/CredentialManager/res/values-nb/strings.xml
+++ b/packages/CredentialManager/res/values-nb/strings.xml
@@ -40,8 +40,7 @@
<string name="other_password_manager" msgid="565790221427004141">"Andre løsninger for passordlagring"</string>
<string name="close_sheet" msgid="1393792015338908262">"Lukk arket"</string>
<string name="accessibility_back_arrow_button" msgid="3233198183497842492">"Gå tilbake til den forrige siden"</string>
- <!-- no translation found for accessibility_close_button (2953807735590034688) -->
- <skip />
+ <string name="accessibility_close_button" msgid="2953807735590034688">"Lukk handlingsforslaget fra legitimasjonslagring som vises nedest på skjermen"</string>
<string name="get_dialog_title_use_passkey_for" msgid="6236608872708021767">"Vil du bruke den lagrede tilgangsnøkkelen for <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
<string name="get_dialog_title_use_sign_in_for" msgid="5283099528915572980">"Vil du bruke den lagrede påloggingen for <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
<string name="get_dialog_title_choose_sign_in_for" msgid="1361715440877613701">"Velg en lagret pålogging for <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
diff --git a/packages/CredentialManager/res/values-ro/strings.xml b/packages/CredentialManager/res/values-ro/strings.xml
index e891d5b..6f3c3a6 100644
--- a/packages/CredentialManager/res/values-ro/strings.xml
+++ b/packages/CredentialManager/res/values-ro/strings.xml
@@ -40,8 +40,7 @@
<string name="other_password_manager" msgid="565790221427004141">"Alți manageri de parole"</string>
<string name="close_sheet" msgid="1393792015338908262">"Închide foaia"</string>
<string name="accessibility_back_arrow_button" msgid="3233198183497842492">"Revino la pagina precedentă"</string>
- <!-- no translation found for accessibility_close_button (2953807735590034688) -->
- <skip />
+ <string name="accessibility_close_button" msgid="2953807735590034688">"Închide sugestia de acțiune de la Managerul de date de conectare, care apare în partea de jos a ecranului"</string>
<string name="get_dialog_title_use_passkey_for" msgid="6236608872708021767">"Folosești cheia de acces salvată pentru <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
<string name="get_dialog_title_use_sign_in_for" msgid="5283099528915572980">"Folosești datele de conectare salvate pentru <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
<string name="get_dialog_title_choose_sign_in_for" msgid="1361715440877613701">"Alege o conectare salvată pentru <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
diff --git a/packages/CredentialManager/res/values-ru/strings.xml b/packages/CredentialManager/res/values-ru/strings.xml
index 6617356..0a9811b 100644
--- a/packages/CredentialManager/res/values-ru/strings.xml
+++ b/packages/CredentialManager/res/values-ru/strings.xml
@@ -40,8 +40,7 @@
<string name="other_password_manager" msgid="565790221427004141">"Другие менеджеры паролей"</string>
<string name="close_sheet" msgid="1393792015338908262">"Закрыть лист"</string>
<string name="accessibility_back_arrow_button" msgid="3233198183497842492">"Вернуться на предыдущую страницу"</string>
- <!-- no translation found for accessibility_close_button (2953807735590034688) -->
- <skip />
+ <string name="accessibility_close_button" msgid="2953807735590034688">"Закрыть подсказку Менеджера учетных данных в нижней части экрана"</string>
<string name="get_dialog_title_use_passkey_for" msgid="6236608872708021767">"Использовать сохраненный ключ доступа для приложения \"<xliff:g id="APP_NAME">%1$s</xliff:g>\"?"</string>
<string name="get_dialog_title_use_sign_in_for" msgid="5283099528915572980">"Использовать сохраненные учетные данные для приложения \"<xliff:g id="APP_NAME">%1$s</xliff:g>\"?"</string>
<string name="get_dialog_title_choose_sign_in_for" msgid="1361715440877613701">"Выберите сохраненные данные для приложения \"<xliff:g id="APP_NAME">%1$s</xliff:g>\""</string>
diff --git a/packages/CredentialManager/res/values-te/strings.xml b/packages/CredentialManager/res/values-te/strings.xml
index 0086710..8a16953 100644
--- a/packages/CredentialManager/res/values-te/strings.xml
+++ b/packages/CredentialManager/res/values-te/strings.xml
@@ -40,8 +40,7 @@
<string name="other_password_manager" msgid="565790221427004141">"ఇతర పాస్వర్డ్ మేనేజర్లు"</string>
<string name="close_sheet" msgid="1393792015338908262">"షీట్ను మూసివేయండి"</string>
<string name="accessibility_back_arrow_button" msgid="3233198183497842492">"మునుపటి పేజీకి తిరిగి వెళ్లండి"</string>
- <!-- no translation found for accessibility_close_button (2953807735590034688) -->
- <skip />
+ <string name="accessibility_close_button" msgid="2953807735590034688">"స్క్రీన్ దిగువున కనిపించే ఆధారాల మేనేజర్ చర్య సూచనను మూసివేయండి"</string>
<string name="get_dialog_title_use_passkey_for" msgid="6236608872708021767">"<xliff:g id="APP_NAME">%1$s</xliff:g> కోసం మీ సేవ్ చేసిన పాస్-కీ వివరాలను ఉపయోగించాలా?"</string>
<string name="get_dialog_title_use_sign_in_for" msgid="5283099528915572980">"<xliff:g id="APP_NAME">%1$s</xliff:g> కోసం మీరు సేవ్ చేసిన సైన్ ఇన్ వివరాలను ఉపయోగించాలా?"</string>
<string name="get_dialog_title_choose_sign_in_for" msgid="1361715440877613701">"<xliff:g id="APP_NAME">%1$s</xliff:g> కోసం సేవ్ చేసిన సైన్ ఇన్ వివరాలను ఎంచుకోండి"</string>
diff --git a/packages/CredentialManager/res/values-th/strings.xml b/packages/CredentialManager/res/values-th/strings.xml
index 0dfda15..6bd0eab 100644
--- a/packages/CredentialManager/res/values-th/strings.xml
+++ b/packages/CredentialManager/res/values-th/strings.xml
@@ -40,8 +40,7 @@
<string name="other_password_manager" msgid="565790221427004141">"เครื่องมือจัดการรหัสผ่านอื่นๆ"</string>
<string name="close_sheet" msgid="1393792015338908262">"ปิดชีต"</string>
<string name="accessibility_back_arrow_button" msgid="3233198183497842492">"กลับไปยังหน้าก่อนหน้า"</string>
- <!-- no translation found for accessibility_close_button (2953807735590034688) -->
- <skip />
+ <string name="accessibility_close_button" msgid="2953807735590034688">"ปิดการแนะนำการดำเนินการของเครื่องมือจัดการข้อมูลเข้าสู่ระบบซึ่งปรากฏที่ด้านล่างของหน้าจอ"</string>
<string name="get_dialog_title_use_passkey_for" msgid="6236608872708021767">"ใช้พาสคีย์ที่บันทึกไว้สำหรับ \"<xliff:g id="APP_NAME">%1$s</xliff:g>\" ใช่ไหม"</string>
<string name="get_dialog_title_use_sign_in_for" msgid="5283099528915572980">"ใช้การลงชื่อเข้าใช้ที่บันทึกไว้สำหรับ \"<xliff:g id="APP_NAME">%1$s</xliff:g>\" ใช่ไหม"</string>
<string name="get_dialog_title_choose_sign_in_for" msgid="1361715440877613701">"เลือกการลงชื่อเข้าใช้ที่บันทึกไว้สำหรับ \"<xliff:g id="APP_NAME">%1$s</xliff:g>\""</string>
diff --git a/packages/CredentialManager/res/values-tl/strings.xml b/packages/CredentialManager/res/values-tl/strings.xml
index 08d17bc..3c8cea8 100644
--- a/packages/CredentialManager/res/values-tl/strings.xml
+++ b/packages/CredentialManager/res/values-tl/strings.xml
@@ -40,8 +40,7 @@
<string name="other_password_manager" msgid="565790221427004141">"Iba pang password manager"</string>
<string name="close_sheet" msgid="1393792015338908262">"Isara ang sheet"</string>
<string name="accessibility_back_arrow_button" msgid="3233198183497842492">"Bumalik sa nakaraang page"</string>
- <!-- no translation found for accessibility_close_button (2953807735590034688) -->
- <skip />
+ <string name="accessibility_close_button" msgid="2953807735590034688">"Isara ang suhestyon sa pagkilos ng Manager ng Kredensyal na lumalabas sa ibaba ng screen"</string>
<string name="get_dialog_title_use_passkey_for" msgid="6236608872708021767">"Gamitin ang iyong naka-save na passkey para sa <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
<string name="get_dialog_title_use_sign_in_for" msgid="5283099528915572980">"Gamitin ang iyong naka-save na sign-in para sa <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
<string name="get_dialog_title_choose_sign_in_for" msgid="1361715440877613701">"Pumili ng naka-save na sign-in para sa <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
diff --git a/packages/CredentialManager/res/values-tr/strings.xml b/packages/CredentialManager/res/values-tr/strings.xml
index 7ec70bf..e6f3de3 100644
--- a/packages/CredentialManager/res/values-tr/strings.xml
+++ b/packages/CredentialManager/res/values-tr/strings.xml
@@ -40,8 +40,7 @@
<string name="other_password_manager" msgid="565790221427004141">"Diğer şifre yöneticileri"</string>
<string name="close_sheet" msgid="1393792015338908262">"Sayfayı kapat"</string>
<string name="accessibility_back_arrow_button" msgid="3233198183497842492">"Önceki sayfaya geri dön"</string>
- <!-- no translation found for accessibility_close_button (2953807735590034688) -->
- <skip />
+ <string name="accessibility_close_button" msgid="2953807735590034688">"Ekranın altında görünen Kimlik Bilgisi Yöneticisi işlem önerisini kapatın"</string>
<string name="get_dialog_title_use_passkey_for" msgid="6236608872708021767">"<xliff:g id="APP_NAME">%1$s</xliff:g> için kayıtlı şifre anahtarınız kullanılsın mı?"</string>
<string name="get_dialog_title_use_sign_in_for" msgid="5283099528915572980">"<xliff:g id="APP_NAME">%1$s</xliff:g> için kayıtlı oturum açma bilgileriniz kullanılsın mı?"</string>
<string name="get_dialog_title_choose_sign_in_for" msgid="1361715440877613701">"<xliff:g id="APP_NAME">%1$s</xliff:g> için kayıtlı oturum açma bilgilerini kullanın"</string>
diff --git a/packages/CredentialManager/res/values-ur/strings.xml b/packages/CredentialManager/res/values-ur/strings.xml
index 05ab98c..5fbb556 100644
--- a/packages/CredentialManager/res/values-ur/strings.xml
+++ b/packages/CredentialManager/res/values-ur/strings.xml
@@ -40,8 +40,7 @@
<string name="other_password_manager" msgid="565790221427004141">"دیگر پاس ورڈ مینیجرز"</string>
<string name="close_sheet" msgid="1393792015338908262">"شیٹ بند کریں"</string>
<string name="accessibility_back_arrow_button" msgid="3233198183497842492">"گزشتہ صفحے پر واپس جائیں"</string>
- <!-- no translation found for accessibility_close_button (2953807735590034688) -->
- <skip />
+ <string name="accessibility_close_button" msgid="2953807735590034688">"اسکرین کے نیچے ظاہر ہونے والے سند مینیجر کی کاروائی کی تجویز بند کریں"</string>
<string name="get_dialog_title_use_passkey_for" msgid="6236608872708021767">"<xliff:g id="APP_NAME">%1$s</xliff:g> کے لیے اپنی محفوظ کردہ پاس کی استعمال کریں؟"</string>
<string name="get_dialog_title_use_sign_in_for" msgid="5283099528915572980">"<xliff:g id="APP_NAME">%1$s</xliff:g> کے لیے اپنے محفوظ کردہ سائن ان کو استعمال کریں؟"</string>
<string name="get_dialog_title_choose_sign_in_for" msgid="1361715440877613701">"<xliff:g id="APP_NAME">%1$s</xliff:g> کے لیے محفوظ کردہ سائن انز منتخب کریں"</string>
diff --git a/packages/CredentialManager/res/values-zh-rCN/strings.xml b/packages/CredentialManager/res/values-zh-rCN/strings.xml
index 703dfc1..7402cce 100644
--- a/packages/CredentialManager/res/values-zh-rCN/strings.xml
+++ b/packages/CredentialManager/res/values-zh-rCN/strings.xml
@@ -40,8 +40,7 @@
<string name="other_password_manager" msgid="565790221427004141">"其他密码管理工具"</string>
<string name="close_sheet" msgid="1393792015338908262">"关闭工作表"</string>
<string name="accessibility_back_arrow_button" msgid="3233198183497842492">"返回上一页"</string>
- <!-- no translation found for accessibility_close_button (2953807735590034688) -->
- <skip />
+ <string name="accessibility_close_button" msgid="2953807735590034688">"关闭屏幕底部显示的 Credential Manager 操作建议"</string>
<string name="get_dialog_title_use_passkey_for" msgid="6236608872708021767">"将您已保存的通行密钥用于<xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
<string name="get_dialog_title_use_sign_in_for" msgid="5283099528915572980">"将您已保存的登录信息用于<xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
<string name="get_dialog_title_choose_sign_in_for" msgid="1361715440877613701">"为<xliff:g id="APP_NAME">%1$s</xliff:g>选择已保存的登录信息"</string>
diff --git a/packages/CredentialManager/res/values-zh-rHK/strings.xml b/packages/CredentialManager/res/values-zh-rHK/strings.xml
index 31d6993..e4009b0 100644
--- a/packages/CredentialManager/res/values-zh-rHK/strings.xml
+++ b/packages/CredentialManager/res/values-zh-rHK/strings.xml
@@ -40,8 +40,7 @@
<string name="other_password_manager" msgid="565790221427004141">"其他密碼管理工具"</string>
<string name="close_sheet" msgid="1393792015338908262">"閂工作表"</string>
<string name="accessibility_back_arrow_button" msgid="3233198183497842492">"返回上一頁"</string>
- <!-- no translation found for accessibility_close_button (2953807735590034688) -->
- <skip />
+ <string name="accessibility_close_button" msgid="2953807735590034688">"關閉顯示在畫面底部的憑證管理工具操作建議"</string>
<string name="get_dialog_title_use_passkey_for" msgid="6236608872708021767">"要使用已儲存的「<xliff:g id="APP_NAME">%1$s</xliff:g>」密鑰嗎?"</string>
<string name="get_dialog_title_use_sign_in_for" msgid="5283099528915572980">"要使用已儲存的「<xliff:g id="APP_NAME">%1$s</xliff:g>」登入資料嗎?"</string>
<string name="get_dialog_title_choose_sign_in_for" msgid="1361715440877613701">"選擇已儲存的「<xliff:g id="APP_NAME">%1$s</xliff:g>」登入資料"</string>
diff --git a/packages/CredentialManager/res/values-zh-rTW/strings.xml b/packages/CredentialManager/res/values-zh-rTW/strings.xml
index 44b921a..dcdfeef 100644
--- a/packages/CredentialManager/res/values-zh-rTW/strings.xml
+++ b/packages/CredentialManager/res/values-zh-rTW/strings.xml
@@ -40,8 +40,7 @@
<string name="other_password_manager" msgid="565790221427004141">"其他密碼管理工具"</string>
<string name="close_sheet" msgid="1393792015338908262">"關閉功能表"</string>
<string name="accessibility_back_arrow_button" msgid="3233198183497842492">"返回上一頁"</string>
- <!-- no translation found for accessibility_close_button (2953807735590034688) -->
- <skip />
+ <string name="accessibility_close_button" msgid="2953807735590034688">"關閉顯示在畫面底部的憑證管理工具操作建議"</string>
<string name="get_dialog_title_use_passkey_for" msgid="6236608872708021767">"要使用已儲存的「<xliff:g id="APP_NAME">%1$s</xliff:g>」密碼金鑰嗎?"</string>
<string name="get_dialog_title_use_sign_in_for" msgid="5283099528915572980">"要使用已儲存的「<xliff:g id="APP_NAME">%1$s</xliff:g>」登入資訊嗎?"</string>
<string name="get_dialog_title_choose_sign_in_for" msgid="1361715440877613701">"選擇已儲存的「<xliff:g id="APP_NAME">%1$s</xliff:g>」登入資訊"</string>
diff --git a/packages/SettingsLib/SelectorWithWidgetPreference/res/values-b+sr+Latn/strings.xml b/packages/SettingsLib/SelectorWithWidgetPreference/res/values-b+sr+Latn/strings.xml
index d51823f..1478a00 100644
--- a/packages/SettingsLib/SelectorWithWidgetPreference/res/values-b+sr+Latn/strings.xml
+++ b/packages/SettingsLib/SelectorWithWidgetPreference/res/values-b+sr+Latn/strings.xml
@@ -17,5 +17,5 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="settings_label" msgid="5948970810295631236">"Podešavanja"</string>
+ <string name="settings_label" msgid="5948970810295631236">"Подешавања"</string>
</resources>
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/PackageManagers.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/PackageManagers.kt
index cb0bfc6..69c6131 100644
--- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/PackageManagers.kt
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/PackageManagers.kt
@@ -21,11 +21,8 @@
import android.content.pm.PackageInfo
import android.content.pm.PackageInfo.REQUESTED_PERMISSION_GRANTED
import android.content.pm.PackageManager
-import android.util.Log
import com.android.settingslib.spa.framework.util.asyncFilter
-private const val TAG = "PackageManagers"
-
interface IPackageManagers {
fun getPackageInfoAsUser(packageName: String, userId: Int): PackageInfo?
fun getApplicationInfoAsUser(packageName: String, userId: Int): ApplicationInfo?
@@ -94,12 +91,7 @@
}.toSet()
override fun getPackageInfoAsUser(packageName: String, flags: Int, userId: Int): PackageInfo? =
- try {
- packageManagerWrapper.getPackageInfoAsUserCached(packageName, flags.toLong(), userId)
- } catch (e: PackageManager.NameNotFoundException) {
- Log.w(TAG, "getPackageInfoAsUserCached() failed", e)
- null
- }
+ packageManagerWrapper.getPackageInfoAsUserCached(packageName, flags.toLong(), userId)
private fun Int.hasFlag(flag: Int) = (this and flag) > 0
}
diff --git a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/AppListRepositoryTest.kt b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/AppListRepositoryTest.kt
index c4f2df2..26174c2 100644
--- a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/AppListRepositoryTest.kt
+++ b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/AppListRepositoryTest.kt
@@ -17,13 +17,17 @@
package com.android.settingslib.spaprivileged.model.app
import android.content.Context
+import android.content.pm.ActivityInfo
import android.content.pm.ApplicationInfo
import android.content.pm.PackageManager
import android.content.pm.PackageManager.ApplicationInfoFlags
import android.content.pm.PackageManager.ResolveInfoFlags
+import android.content.pm.ResolveInfo
import androidx.test.ext.junit.runners.AndroidJUnit4
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.first
+import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.test.runTest
import org.junit.Before
import org.junit.Rule
@@ -51,50 +55,162 @@
private lateinit var repository: AppListRepository
- private val normalApp = ApplicationInfo().apply {
- packageName = "normal"
- enabled = true
- }
-
- private val instantApp = ApplicationInfo().apply {
- packageName = "instant"
- enabled = true
- privateFlags = ApplicationInfo.PRIVATE_FLAG_INSTANT
- }
-
@Before
fun setUp() {
whenever(context.packageManager).thenReturn(packageManager)
whenever(packageManager.getInstalledModules(anyInt())).thenReturn(emptyList())
whenever(
- packageManager.getInstalledApplicationsAsUser(any<ApplicationInfoFlags>(), eq(USER_ID))
- ).thenReturn(listOf(normalApp, instantApp))
- whenever(
packageManager.queryIntentActivitiesAsUser(any(), any<ResolveInfoFlags>(), eq(USER_ID))
).thenReturn(emptyList())
repository = AppListRepositoryImpl(context)
}
+ private fun mockInstalledApplications(apps: List<ApplicationInfo>) {
+ whenever(
+ packageManager.getInstalledApplicationsAsUser(any<ApplicationInfoFlags>(), eq(USER_ID))
+ ).thenReturn(apps)
+ }
+
@Test
- fun notShowInstantApps() = runTest {
+ fun loadApps_notShowInstantApps() = runTest {
+ mockInstalledApplications(listOf(NORMAL_APP, INSTANT_APP))
val appListConfig = AppListConfig(userId = USER_ID, showInstantApps = false)
val appListFlow = repository.loadApps(appListConfig)
- assertThat(appListFlow).containsExactly(normalApp)
+ assertThat(appListFlow).containsExactly(NORMAL_APP)
}
@Test
- fun showInstantApps() = runTest {
+ fun loadApps_showInstantApps() = runTest {
+ mockInstalledApplications(listOf(NORMAL_APP, INSTANT_APP))
val appListConfig = AppListConfig(userId = USER_ID, showInstantApps = true)
val appListFlow = repository.loadApps(appListConfig)
- assertThat(appListFlow).containsExactly(normalApp, instantApp)
+ assertThat(appListFlow).containsExactly(NORMAL_APP, INSTANT_APP)
}
+ @Test
+ fun loadApps_isDisabledUntilUsed() = runTest {
+ val app = ApplicationInfo().apply {
+ packageName = "is.disabled.until.used"
+ enabled = false
+ enabledSetting = PackageManager.COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED
+ }
+ mockInstalledApplications(listOf(app))
+ val appListConfig = AppListConfig(userId = USER_ID, showInstantApps = false)
+
+ val appListFlow = repository.loadApps(appListConfig)
+
+ assertThat(appListFlow).containsExactly(app)
+ }
+
+ @Test
+ fun loadApps_disabled() = runTest {
+ val app = ApplicationInfo().apply {
+ packageName = "disabled"
+ enabled = false
+ }
+ mockInstalledApplications(listOf(app))
+ val appListConfig = AppListConfig(userId = USER_ID, showInstantApps = false)
+
+ val appListFlow = repository.loadApps(appListConfig)
+
+ assertThat(appListFlow).isEmpty()
+ }
+
+ @Test
+ fun showSystemPredicate_showSystem() = runTest {
+ val app = ApplicationInfo().apply {
+ flags = ApplicationInfo.FLAG_SYSTEM
+ }
+
+ val showSystemPredicate = getShowSystemPredicate(showSystem = true)
+
+ assertThat(showSystemPredicate(app)).isTrue()
+ }
+
+ @Test
+ fun showSystemPredicate_notShowSystemAndIsSystemApp() = runTest {
+ val app = ApplicationInfo().apply {
+ flags = ApplicationInfo.FLAG_SYSTEM
+ }
+
+ val showSystemPredicate = getShowSystemPredicate(showSystem = false)
+
+ assertThat(showSystemPredicate(app)).isFalse()
+ }
+
+ @Test
+ fun showSystemPredicate_isUpdatedSystemApp() = runTest {
+ val app = ApplicationInfo().apply {
+ flags = ApplicationInfo.FLAG_SYSTEM or ApplicationInfo.FLAG_UPDATED_SYSTEM_APP
+ }
+
+ val showSystemPredicate = getShowSystemPredicate(showSystem = false)
+
+ assertThat(showSystemPredicate(app)).isTrue()
+ }
+
+ @Test
+ fun showSystemPredicate_isHome() = runTest {
+ val app = ApplicationInfo().apply {
+ flags = ApplicationInfo.FLAG_SYSTEM
+ packageName = "home.app"
+ }
+ whenever(packageManager.getHomeActivities(any())).thenAnswer {
+ @Suppress("UNCHECKED_CAST")
+ val resolveInfos = it.arguments[0] as MutableList<ResolveInfo>
+ resolveInfos.add(resolveInfoOf(packageName = app.packageName))
+ null
+ }
+
+ val showSystemPredicate = getShowSystemPredicate(showSystem = false)
+
+ assertThat(showSystemPredicate(app)).isTrue()
+ }
+
+ @Test
+ fun showSystemPredicate_appInLauncher() = runTest {
+ val app = ApplicationInfo().apply {
+ flags = ApplicationInfo.FLAG_SYSTEM
+ packageName = "app.in.launcher"
+ }
+ whenever(
+ packageManager.queryIntentActivitiesAsUser(any(), any<ResolveInfoFlags>(), eq(USER_ID))
+ ).thenReturn(listOf(resolveInfoOf(packageName = app.packageName)))
+
+ val showSystemPredicate = getShowSystemPredicate(showSystem = false)
+
+ assertThat(showSystemPredicate(app)).isTrue()
+ }
+
+ private suspend fun getShowSystemPredicate(showSystem: Boolean) =
+ repository.showSystemPredicate(
+ userIdFlow = flowOf(USER_ID),
+ showSystemFlow = flowOf(showSystem),
+ ).first()
+
private companion object {
const val USER_ID = 0
+
+ val NORMAL_APP = ApplicationInfo().apply {
+ packageName = "normal"
+ enabled = true
+ }
+
+ val INSTANT_APP = ApplicationInfo().apply {
+ packageName = "instant"
+ enabled = true
+ privateFlags = ApplicationInfo.PRIVATE_FLAG_INSTANT
+ }
+
+ fun resolveInfoOf(packageName: String) = ResolveInfo().apply {
+ activityInfo = ActivityInfo().apply {
+ this.packageName = packageName
+ }
+ }
}
}
diff --git a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/ApplicationInfosTest.kt b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/ApplicationInfosTest.kt
new file mode 100644
index 0000000..a1b2df3
--- /dev/null
+++ b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/ApplicationInfosTest.kt
@@ -0,0 +1,157 @@
+/*
+ * Copyright (C) 2022 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.spaprivileged.model.app
+
+import android.app.admin.DevicePolicyManager
+import android.content.Context
+import android.content.pm.ApplicationInfo
+import android.content.pm.PackageManager
+import android.os.UserManager
+import androidx.test.core.app.ApplicationProvider
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.android.settingslib.spaprivileged.framework.common.devicePolicyManager
+import com.android.settingslib.spaprivileged.framework.common.userManager
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.Spy
+import org.mockito.junit.MockitoJUnit
+import org.mockito.junit.MockitoRule
+import org.mockito.Mockito.`when` as whenever
+
+@RunWith(AndroidJUnit4::class)
+class ApplicationInfosTest {
+ @get:Rule
+ val mockito: MockitoRule = MockitoJUnit.rule()
+
+ @Spy
+ private val context: Context = ApplicationProvider.getApplicationContext()
+
+ @Mock
+ private lateinit var userManager: UserManager
+
+ @Mock
+ private lateinit var devicePolicyManager: DevicePolicyManager
+
+ @Before
+ fun setUp() {
+ whenever(context.userManager).thenReturn(userManager)
+ whenever(context.devicePolicyManager).thenReturn(devicePolicyManager)
+ }
+
+ @Test
+ fun userId() {
+ val app = ApplicationInfo().apply {
+ uid = 123
+ }
+
+ val userId = app.userId
+
+ assertThat(userId).isEqualTo(0)
+ }
+
+ @Test
+ fun userHandle() {
+ val app = ApplicationInfo().apply {
+ uid = 123
+ }
+
+ val userHandle = app.userHandle
+
+ assertThat(userHandle.identifier).isEqualTo(0)
+ }
+
+ @Test
+ fun hasFlag() {
+ val app = ApplicationInfo().apply {
+ flags = ApplicationInfo.FLAG_INSTALLED
+ }
+
+ val hasFlag = app.hasFlag(ApplicationInfo.FLAG_INSTALLED)
+
+ assertThat(hasFlag).isTrue()
+ }
+
+ @Test
+ fun installed() {
+ val app = ApplicationInfo().apply {
+ flags = ApplicationInfo.FLAG_INSTALLED
+ }
+
+ val installed = app.installed
+
+ assertThat(installed).isTrue()
+ }
+
+ @Test
+ fun isDisabledUntilUsed() {
+ val app = ApplicationInfo().apply {
+ enabledSetting = PackageManager.COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED
+ }
+
+ val isDisabledUntilUsed = app.isDisabledUntilUsed
+
+ assertThat(isDisabledUntilUsed).isTrue()
+ }
+
+ @Test
+ fun isDisallowControl() {
+ val app = ApplicationInfo().apply {
+ uid = 123
+ }
+ whenever(
+ userManager.hasBaseUserRestriction(UserManager.DISALLOW_APPS_CONTROL, app.userHandle)
+ ).thenReturn(true)
+
+ val isDisallowControl = app.isDisallowControl(context)
+
+ assertThat(isDisallowControl).isTrue()
+ }
+
+ @Test
+ fun isActiveAdmin() {
+ val app = ApplicationInfo().apply {
+ packageName = PACKAGE_NAME
+ uid = 123
+ }
+ whenever(devicePolicyManager.packageHasActiveAdmins(PACKAGE_NAME, app.userId))
+ .thenReturn(true)
+
+ val isActiveAdmin = app.isActiveAdmin(context)
+
+ assertThat(isActiveAdmin).isTrue()
+ }
+
+ @Test
+ fun toRoute() {
+ val app = ApplicationInfo().apply {
+ packageName = PACKAGE_NAME
+ uid = 123
+ }
+
+ val route = app.toRoute()
+
+ assertThat(route).isEqualTo("package.name/0")
+ }
+
+ private companion object {
+ const val PACKAGE_NAME = "package.name"
+ }
+}
diff --git a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/PackageManagersTest.kt b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/PackageManagersTest.kt
index 6c31f4b..7c92838 100644
--- a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/PackageManagersTest.kt
+++ b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/PackageManagersTest.kt
@@ -31,6 +31,74 @@
private val packageManagersImpl = PackageManagersImpl(fakePackageManagerWrapper)
@Test
+ fun getPackageInfoAsUser_notFound() {
+ fakePackageManagerWrapper.fakePackageInfo = null
+
+ val packageInfo = packageManagersImpl.getPackageInfoAsUser(PACKAGE_NAME, 0)
+
+ assertThat(packageInfo).isNull()
+ }
+
+ @Test
+ fun getPackageInfoAsUser_found() {
+ fakePackageManagerWrapper.fakePackageInfo = PackageInfo()
+
+ val packageInfo = packageManagersImpl.getPackageInfoAsUser(PACKAGE_NAME, 0)
+
+ assertThat(packageInfo).isSameInstanceAs(fakePackageManagerWrapper.fakePackageInfo)
+ }
+
+ @Test
+ fun hasRequestPermission_packageInfoIsNull_returnFalse() {
+ fakePackageManagerWrapper.fakePackageInfo = null
+
+ val hasRequestPermission = with(packageManagersImpl) {
+ APP.hasRequestPermission(PERMISSION_A)
+ }
+
+ assertThat(hasRequestPermission).isFalse()
+ }
+
+ @Test
+ fun hasRequestPermission_requestedPermissionsIsNull_returnFalse() {
+ fakePackageManagerWrapper.fakePackageInfo = PackageInfo().apply {
+ requestedPermissions = null
+ }
+
+ val hasRequestPermission = with(packageManagersImpl) {
+ APP.hasRequestPermission(PERMISSION_A)
+ }
+
+ assertThat(hasRequestPermission).isFalse()
+ }
+
+ @Test
+ fun hasRequestPermission_notRequested_returnFalse() {
+ fakePackageManagerWrapper.fakePackageInfo = PackageInfo().apply {
+ requestedPermissions = emptyArray()
+ }
+
+ val hasRequestPermission = with(packageManagersImpl) {
+ APP.hasRequestPermission(PERMISSION_A)
+ }
+
+ assertThat(hasRequestPermission).isFalse()
+ }
+
+ @Test
+ fun hasRequestPermission_requested_returnTrue() {
+ fakePackageManagerWrapper.fakePackageInfo = PackageInfo().apply {
+ requestedPermissions = arrayOf(PERMISSION_A)
+ }
+
+ val hasRequestPermission = with(packageManagersImpl) {
+ APP.hasRequestPermission(PERMISSION_A)
+ }
+
+ assertThat(hasRequestPermission).isTrue()
+ }
+
+ @Test
fun hasGrantPermission_packageInfoIsNull_returnFalse() {
fakePackageManagerWrapper.fakePackageInfo = null
@@ -43,7 +111,9 @@
@Test
fun hasGrantPermission_requestedPermissionsIsNull_returnFalse() {
- fakePackageManagerWrapper.fakePackageInfo = PackageInfo()
+ fakePackageManagerWrapper.fakePackageInfo = PackageInfo().apply {
+ requestedPermissions = null
+ }
val hasGrantPermission = with(packageManagersImpl) {
APP.hasGrantPermission(PERMISSION_A)
@@ -94,18 +164,8 @@
assertThat(hasGrantPermission).isTrue()
}
- private inner class FakePackageManagerWrapper : PackageManagerWrapper {
- var fakePackageInfo: PackageInfo? = null
-
- override fun getPackageInfoAsUserCached(
- packageName: String,
- flags: Long,
- userId: Int,
- ): PackageInfo? = fakePackageInfo
- }
-
private companion object {
- const val PACKAGE_NAME = "packageName"
+ const val PACKAGE_NAME = "package.name"
const val PERMISSION_A = "permission.A"
const val PERMISSION_B = "permission.B"
const val UID = 123
@@ -115,3 +175,10 @@
}
}
}
+
+private class FakePackageManagerWrapper : PackageManagerWrapper {
+ var fakePackageInfo: PackageInfo? = null
+
+ override fun getPackageInfoAsUserCached(packageName: String, flags: Long, userId: Int) =
+ fakePackageInfo
+}
diff --git a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/AppOpPermissionAppListTest.kt b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/AppOpPermissionAppListTest.kt
index f1d9abe..cd9c048 100644
--- a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/AppOpPermissionAppListTest.kt
+++ b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/AppOpPermissionAppListTest.kt
@@ -21,7 +21,7 @@
import android.content.pm.ApplicationInfo
import androidx.compose.runtime.State
import androidx.compose.ui.test.junit4.createComposeRule
-import androidx.lifecycle.liveData
+import androidx.lifecycle.MutableLiveData
import androidx.test.core.app.ApplicationProvider
import androidx.test.ext.junit.runners.AndroidJUnit4
import com.android.settingslib.spa.testutils.firstWithTimeoutOrNull
@@ -254,7 +254,7 @@
private class FakeAppOpsController(private val fakeMode: Int) : IAppOpsController {
var setAllowedCalledWith: Boolean? = null
- override val mode = liveData { emit(fakeMode) }
+ override val mode = MutableLiveData(fakeMode)
override fun setAllowed(allowed: Boolean) {
setAllowedCalledWith = allowed
diff --git a/packages/SettingsLib/res/values-am/strings.xml b/packages/SettingsLib/res/values-am/strings.xml
index 3828459..82e4585 100644
--- a/packages/SettingsLib/res/values-am/strings.xml
+++ b/packages/SettingsLib/res/values-am/strings.xml
@@ -467,8 +467,7 @@
<string name="power_charging" msgid="6727132649743436802">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="STATE">%2$s</xliff:g>"</string>
<string name="power_remaining_charging_duration_only" msgid="8085099012811384899">"እስኪሞላ ድረስ <xliff:g id="TIME">%1$s</xliff:g> ይቀራል"</string>
<string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g> - እስኪሞላ ድረስ <xliff:g id="TIME">%2$s</xliff:g> ይቀራል"</string>
- <!-- no translation found for power_charging_limited (6732738149313642521) -->
- <skip />
+ <string name="power_charging_limited" msgid="6732738149313642521">"<xliff:g id="LEVEL">%1$s</xliff:g> - ኃይል መሙላት ባለበት ቆሟል"</string>
<string name="power_charging_future_paused" msgid="6829683663982987290">"<xliff:g id="LEVEL">%1$s</xliff:g> - እስከ <xliff:g id="DOCK_DEFENDER_THRESHOLD">%2$s</xliff:g> ድረስ ኃይል መሙላት"</string>
<string name="battery_info_status_unknown" msgid="268625384868401114">"ያልታወቀ"</string>
<string name="battery_info_status_charging" msgid="4279958015430387405">"ኃይል በመሙላት ላይ"</string>
diff --git a/packages/SettingsLib/src/com/android/settingslib/Utils.java b/packages/SettingsLib/src/com/android/settingslib/Utils.java
index a78256a..8a30a3b 100644
--- a/packages/SettingsLib/src/com/android/settingslib/Utils.java
+++ b/packages/SettingsLib/src/com/android/settingslib/Utils.java
@@ -23,6 +23,9 @@
import android.graphics.ColorMatrix;
import android.graphics.ColorMatrixColorFilter;
import android.graphics.drawable.Drawable;
+import android.hardware.usb.UsbManager;
+import android.hardware.usb.UsbPort;
+import android.hardware.usb.UsbPortStatus;
import android.location.LocationManager;
import android.media.AudioManager;
import android.net.NetworkCapabilities;
@@ -40,6 +43,7 @@
import android.telephony.NetworkRegistrationInfo;
import android.telephony.ServiceState;
import android.telephony.TelephonyManager;
+import android.util.Log;
import androidx.annotation.NonNull;
import androidx.annotation.RequiresApi;
@@ -55,6 +59,7 @@
import com.android.settingslib.utils.BuildCompatUtils;
import java.text.NumberFormat;
+import java.util.List;
public class Utils {
@@ -308,8 +313,16 @@
@ColorInt
public static int getColorAttrDefaultColor(Context context, int attr) {
+ return getColorAttrDefaultColor(context, attr, 0);
+ }
+
+ /**
+ * Get color styled attribute {@code attr}, default to {@code defValue} if not found.
+ */
+ @ColorInt
+ public static int getColorAttrDefaultColor(Context context, int attr, @ColorInt int defValue) {
TypedArray ta = context.obtainStyledAttributes(new int[]{attr});
- @ColorInt int colorAccent = ta.getColor(0, 0);
+ @ColorInt int colorAccent = ta.getColor(0, defValue);
ta.recycle();
return colorAccent;
}
@@ -630,4 +643,31 @@
(VcnTransportInfo) networkCapabilities.getTransportInfo();
return vcnTransportInfo.getWifiInfo();
}
+
+ /** Whether there is any incompatible chargers in the current UsbPort? */
+ public static boolean containsIncompatibleChargers(Context context, String tag) {
+ final List<UsbPort> usbPortList =
+ context.getSystemService(UsbManager.class).getPorts();
+ if (usbPortList == null || usbPortList.isEmpty()) {
+ return false;
+ }
+ for (UsbPort usbPort : usbPortList) {
+ Log.d(tag, "usbPort: " + usbPort);
+ final UsbPortStatus usbStatus = usbPort.getStatus();
+ if (usbStatus == null || !usbStatus.isConnected()) {
+ continue;
+ }
+ final int[] complianceWarnings = usbStatus.getComplianceWarnings();
+ if (complianceWarnings == null || complianceWarnings.length == 0) {
+ continue;
+ }
+ for (int complianceWarningType : complianceWarnings) {
+ if (complianceWarningType != 0) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/mobile/MobileMappings.java b/packages/SettingsLib/src/com/android/settingslib/mobile/MobileMappings.java
index c941954..840c936 100644
--- a/packages/SettingsLib/src/com/android/settingslib/mobile/MobileMappings.java
+++ b/packages/SettingsLib/src/com/android/settingslib/mobile/MobileMappings.java
@@ -251,5 +251,22 @@
}
return config;
}
+
+ /**
+ * Returns true if this config and the other config are semantically equal.
+ *
+ * Does not override isEquals because existing clients may be relying on the currently
+ * defined equals behavior.
+ */
+ public boolean areEqual(Config other) {
+ return showAtLeast3G == other.showAtLeast3G
+ && show4gFor3g == other.show4gFor3g
+ && alwaysShowCdmaRssi == other.alwaysShowCdmaRssi
+ && show4gForLte == other.show4gForLte
+ && show4glteForLte == other.show4glteForLte
+ && hideLtePlus == other.hideLtePlus
+ && hspaDataDistinguishable == other.hspaDataDistinguishable
+ && alwaysShowDataRatIcon == other.alwaysShowDataRatIcon;
+ }
}
}
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/UtilsTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/UtilsTest.java
index 291f6a3..68a1e19 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/UtilsTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/UtilsTest.java
@@ -29,6 +29,9 @@
import android.content.Context;
import android.content.Intent;
import android.content.res.Resources;
+import android.hardware.usb.UsbManager;
+import android.hardware.usb.UsbPort;
+import android.hardware.usb.UsbPortStatus;
import android.location.LocationManager;
import android.media.AudioManager;
import android.os.BatteryManager;
@@ -53,7 +56,9 @@
import org.robolectric.annotation.Implements;
import org.robolectric.shadows.ShadowSettings;
+import java.util.ArrayList;
import java.util.HashMap;
+import java.util.List;
import java.util.Map;
@RunWith(RobolectricTestRunner.class)
@@ -74,12 +79,19 @@
private ServiceState mServiceState;
@Mock
private NetworkRegistrationInfo mNetworkRegistrationInfo;
+ @Mock
+ private UsbPort mUsbPort;
+ @Mock
+ private UsbManager mUsbManager;
+ @Mock
+ private UsbPortStatus mUsbPortStatus;
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
mContext = spy(RuntimeEnvironment.application);
when(mContext.getSystemService(Context.LOCATION_SERVICE)).thenReturn(mLocationManager);
+ when(mContext.getSystemService(UsbManager.class)).thenReturn(mUsbManager);
ShadowSecure.reset();
mAudioManager = mContext.getSystemService(AudioManager.class);
}
@@ -411,4 +423,50 @@
assertThat(Utils.getBatteryStatus(mContext, intent, /* compactStatus= */ true)).isEqualTo(
resources.getString(R.string.battery_info_status_charging));
}
+
+ @Test
+ public void containsIncompatibleChargers_nullPorts_returnFalse() {
+ when(mUsbManager.getPorts()).thenReturn(null);
+ assertThat(Utils.containsIncompatibleChargers(mContext, "tag")).isFalse();
+ }
+
+ @Test
+ public void containsIncompatibleChargers_emptyPorts_returnFalse() {
+ when(mUsbManager.getPorts()).thenReturn(new ArrayList<>());
+ assertThat(Utils.containsIncompatibleChargers(mContext, "tag")).isFalse();
+ }
+
+ @Test
+ public void containsIncompatibleChargers_nullPortStatus_returnFalse() {
+ final List<UsbPort> usbPorts = new ArrayList<>();
+ usbPorts.add(mUsbPort);
+ when(mUsbManager.getPorts()).thenReturn(usbPorts);
+ when(mUsbPort.getStatus()).thenReturn(null);
+
+ assertThat(Utils.containsIncompatibleChargers(mContext, "tag")).isFalse();
+ }
+
+ @Test
+ public void containsIncompatibleChargers_returnTrue() {
+ final List<UsbPort> usbPorts = new ArrayList<>();
+ usbPorts.add(mUsbPort);
+ when(mUsbManager.getPorts()).thenReturn(usbPorts);
+ when(mUsbPort.getStatus()).thenReturn(mUsbPortStatus);
+ when(mUsbPortStatus.isConnected()).thenReturn(true);
+ when(mUsbPortStatus.getComplianceWarnings()).thenReturn(new int[]{1});
+
+ assertThat(Utils.containsIncompatibleChargers(mContext, "tag")).isTrue();
+ }
+
+ @Test
+ public void containsIncompatibleChargers_emptyComplianceWarnings_returnFalse() {
+ final List<UsbPort> usbPorts = new ArrayList<>();
+ usbPorts.add(mUsbPort);
+ when(mUsbManager.getPorts()).thenReturn(usbPorts);
+ when(mUsbPort.getStatus()).thenReturn(mUsbPortStatus);
+ when(mUsbPortStatus.isConnected()).thenReturn(true);
+ when(mUsbPortStatus.getComplianceWarnings()).thenReturn(new int[1]);
+
+ assertThat(Utils.containsIncompatibleChargers(mContext, "tag")).isFalse();
+ }
}
diff --git a/packages/Shell/AndroidManifest.xml b/packages/Shell/AndroidManifest.xml
index c7db275..b8ac384 100644
--- a/packages/Shell/AndroidManifest.xml
+++ b/packages/Shell/AndroidManifest.xml
@@ -515,6 +515,9 @@
<!-- Permissions needed for CTS test - CtsLocaleManagerTestCases -->
<uses-permission android:name="android.permission.READ_APP_SPECIFIC_LOCALES" />
+ <!-- Permissions needed for CTS test - CtsLocaleManagerTestCases -->
+ <uses-permission android:name="android.permission.SET_APP_SPECIFIC_LOCALECONFIG" />
+
<!-- Permissions used for manual testing of time detection behavior. -->
<uses-permission android:name="android.permission.SUGGEST_MANUAL_TIME" />
<uses-permission android:name="android.permission.SUGGEST_TELEPHONY_TIME" />
diff --git a/packages/SystemUI/res/layout/media_session_view.xml b/packages/SystemUI/res/layout/media_session_view.xml
index 13c9a5e..95aefab 100644
--- a/packages/SystemUI/res/layout/media_session_view.xml
+++ b/packages/SystemUI/res/layout/media_session_view.xml
@@ -186,7 +186,7 @@
app:layout_constraintBottom_toBottomOf="parent"
app:barrierDirection="end"
app:constraint_referenced_ids="actionPrev,media_scrubbing_elapsed_time,media_progress_bar,actionNext,media_scrubbing_total_time,action0,action1,action2,action3,action4"
- app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintRight_toRightOf="@id/actionPlayPause"
/>
<!-- This barrier is used in expanded view to constrain the bottom row of actions -->
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index 4f64a4b..15ba4aa 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -2510,6 +2510,8 @@
<string name="media_output_dialog_accessibility_seekbar">Volume</string>
<!-- Summary for media output volume of a device in percentage [CHAR LIMIT=NONE] -->
<string name="media_output_dialog_volume_percentage"><xliff:g id="percentage" example="10">%1$d</xliff:g>%%</string>
+ <!-- Title for Speakers and Displays group. [CHAR LIMIT=NONE] -->
+ <string name="media_output_group_title_speakers_and_displays">Speakers & Displays</string>
<!-- Media Output Broadcast Dialog -->
<!-- Title for Broadcast First Notify Dialog [CHAR LIMIT=60] -->
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt b/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt
index c0b0571..96707f4 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt
@@ -43,6 +43,7 @@
import com.android.systemui.shortcut.ShortcutKeyDispatcher
import com.android.systemui.statusbar.notification.fsi.FsiChromeRepo
import com.android.systemui.statusbar.notification.InstantAppNotifier
+import com.android.systemui.statusbar.notification.fsi.FsiChromeViewModelFactory
import com.android.systemui.statusbar.phone.KeyguardLiftController
import com.android.systemui.stylus.StylusUsiPowerStartable
import com.android.systemui.temporarydisplay.chipbar.ChipbarCoordinator
@@ -87,6 +88,12 @@
@ClassKey(FsiChromeRepo::class)
abstract fun bindFSIChromeRepo(sysui: FsiChromeRepo): CoreStartable
+ /** Inject into FsiChromeWindowViewModel. */
+ @Binds
+ @IntoMap
+ @ClassKey(FsiChromeViewModelFactory::class)
+ abstract fun bindFSIChromeWindowViewModel(sysui: FsiChromeViewModelFactory): CoreStartable
+
/** Inject into GarbageMonitor.Service. */
@Binds
@IntoMap
diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
index ae1160f..64911ec 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
@@ -183,6 +183,18 @@
// TODO(b/260619425): Tracking Bug
@JvmField val MODERN_ALTERNATE_BOUNCER = unreleasedFlag(219, "modern_alternate_bouncer")
+ // TODO(b/262780002): Tracking Bug
+ @JvmField
+ val REVAMPED_WALLPAPER_UI = unreleasedFlag(222, "revamped_wallpaper_ui", teamfood = false)
+
+ /** Flag to control the migration of face auth to modern architecture. */
+ // TODO(b/262838215): Tracking bug
+ @JvmField val FACE_AUTH_REFACTOR = unreleasedFlag(220, "face_auth_refactor")
+
+ /** Flag to control the revamp of keyguard biometrics progress animation */
+ // TODO(b/244313043): Tracking bug
+ @JvmField val BIOMETRICS_ANIMATION_REVAMP = unreleasedFlag(221, "biometrics_animation_revamp")
+
// 300 - power menu
// TODO(b/254512600): Tracking Bug
@JvmField val POWER_MENU_LITE = releasedFlag(300, "power_menu_lite")
@@ -272,7 +284,7 @@
// 900 - media
// TODO(b/254512697): Tracking Bug
- val MEDIA_TAP_TO_TRANSFER = releasedFlag(900, "media_tap_to_transfer")
+ val MEDIA_TAP_TO_TRANSFER = unreleasedFlag(900, "media_tap_to_transfer", teamfood = true)
// TODO(b/254512502): Tracking Bug
val MEDIA_SESSION_ACTIONS = unreleasedFlag(901, "media_session_actions")
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/LifecycleScreenStatusProvider.kt b/packages/SystemUI/src/com/android/systemui/keyguard/LifecycleScreenStatusProvider.kt
index 0b04fb4..6063ce2 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/LifecycleScreenStatusProvider.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/LifecycleScreenStatusProvider.kt
@@ -17,6 +17,7 @@
import com.android.systemui.unfold.updates.screen.ScreenStatusProvider
import com.android.systemui.unfold.updates.screen.ScreenStatusProvider.ScreenListener
+import com.android.systemui.util.traceSection
import javax.inject.Inject
import javax.inject.Singleton
@@ -39,14 +40,22 @@
}
override fun onScreenTurnedOn() {
- listeners.forEach(ScreenListener::onScreenTurnedOn)
+ traceSection("$TRACE_TAG#onScreenTurnedOn") {
+ listeners.forEach(ScreenListener::onScreenTurnedOn)
+ }
}
override fun onScreenTurningOff() {
- listeners.forEach(ScreenListener::onScreenTurningOff)
+ traceSection("$TRACE_TAG#onScreenTurningOff") {
+ listeners.forEach(ScreenListener::onScreenTurningOff)
+ }
}
override fun onScreenTurningOn() {
- listeners.forEach(ScreenListener::onScreenTurningOn)
+ traceSection("$TRACE_TAG#onScreenTurningOn") {
+ listeners.forEach(ScreenListener::onScreenTurningOn)
+ }
}
}
+
+private const val TRACE_TAG = "LifecycleScreenStatusProvider"
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java b/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java
index f2e8e42..7beb5b8 100644
--- a/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java
+++ b/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java
@@ -102,6 +102,15 @@
return factory.create("ShadeLog", 500, false);
}
+ /** Provides a logging buffer for Shade height messages. */
+ @Provides
+ @SysUISingleton
+ @ShadeHeightLog
+ public static LogBuffer provideShadeHeightLogBuffer(LogBufferFactory factory) {
+ return factory.create("ShadeHeightLog", 500 /* maxSize */);
+ }
+
+
/** Provides a logging buffer for all logs related to managing notification sections. */
@Provides
@SysUISingleton
diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/ShadeHeightLog.java b/packages/SystemUI/src/com/android/systemui/log/dagger/ShadeHeightLog.java
new file mode 100644
index 0000000..6fe8686
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/log/dagger/ShadeHeightLog.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2022 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.log.dagger;
+
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+
+import com.android.systemui.plugins.log.LogBuffer;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.Retention;
+
+import javax.inject.Qualifier;
+
+/** A {@link LogBuffer} for Shade height changes. */
+@Qualifier
+@Documented
+@Retention(RUNTIME)
+public @interface ShadeHeightLog {
+}
diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputAdapter.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputAdapter.java
index fb47d97..51b5a3d 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputAdapter.java
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputAdapter.java
@@ -70,6 +70,13 @@
@Override
public void onBindViewHolder(@NonNull RecyclerView.ViewHolder viewHolder, int position) {
if (mController.isAdvancedLayoutSupported()) {
+ if (position >= mController.getMediaItemList().size()) {
+ if (DEBUG) {
+ Log.d(TAG, "Incorrect position: " + position + " list size: "
+ + mController.getMediaItemList().size());
+ }
+ return;
+ }
MediaItem currentMediaItem = mController.getMediaItemList().get(position);
switch (currentMediaItem.getMediaItemType()) {
case MediaItem.MediaItemType.TYPE_GROUP_DIVIDER:
diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseAdapter.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseAdapter.java
index 3b1d861..4e08050 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseAdapter.java
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseAdapter.java
@@ -84,8 +84,7 @@
int viewType) {
mContext = viewGroup.getContext();
mHolderView = LayoutInflater.from(mContext).inflate(
- mController.isAdvancedLayoutSupported() ? MediaItem.getMediaLayoutId(
- viewType) /*R.layout.media_output_list_item_advanced*/
+ mController.isAdvancedLayoutSupported() ? MediaItem.getMediaLayoutId(viewType)
: R.layout.media_output_list_item, viewGroup, false);
return null;
@@ -308,9 +307,10 @@
updateTitleIcon(currentVolume == 0 ? R.drawable.media_output_icon_volume_off
: R.drawable.media_output_icon_volume,
mController.getColorItemContent());
+ } else {
+ animateCornerAndVolume(mSeekBar.getProgress(),
+ MediaOutputSeekbar.scaleVolumeToProgress(currentVolume));
}
- animateCornerAndVolume(mSeekBar.getProgress(),
- MediaOutputSeekbar.scaleVolumeToProgress(currentVolume));
} else {
if (!mVolumeAnimator.isStarted()) {
if (mController.isAdvancedLayoutSupported()) {
@@ -396,23 +396,18 @@
}
void updateMutedVolumeIcon() {
+ mIconAreaLayout.setBackground(
+ mContext.getDrawable(R.drawable.media_output_item_background_active));
updateTitleIcon(R.drawable.media_output_icon_volume_off,
mController.getColorItemContent());
- final GradientDrawable iconAreaBackgroundDrawable =
- (GradientDrawable) mIconAreaLayout.getBackground();
- iconAreaBackgroundDrawable.setCornerRadius(mController.getActiveRadius());
}
void updateUnmutedVolumeIcon() {
+ mIconAreaLayout.setBackground(
+ mContext.getDrawable(R.drawable.media_output_title_icon_area)
+ );
updateTitleIcon(R.drawable.media_output_icon_volume,
mController.getColorItemContent());
- final GradientDrawable iconAreaBackgroundDrawable =
- (GradientDrawable) mIconAreaLayout.getBackground();
- iconAreaBackgroundDrawable.setCornerRadii(new float[]{
- mController.getActiveRadius(),
- mController.getActiveRadius(),
- 0, 0, 0, 0, mController.getActiveRadius(), mController.getActiveRadius()
- });
}
void updateTitleIcon(@DrawableRes int id, int color) {
diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java
index b436562..8eb25c4 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java
@@ -87,9 +87,11 @@
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
+import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
+import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.Executor;
@@ -275,7 +277,9 @@
@Override
public void onDeviceListUpdate(List<MediaDevice> devices) {
- if (mMediaDevices.isEmpty() || !mIsRefreshing) {
+ boolean isListEmpty =
+ isAdvancedLayoutSupported() ? mMediaItemList.isEmpty() : mMediaDevices.isEmpty();
+ if (isListEmpty || !mIsRefreshing) {
buildMediaDevices(devices);
mCallback.onDeviceListChanged();
} else {
@@ -646,16 +650,19 @@
for (MediaDevice device : devices) {
if (device.isMutingExpectedDevice()) {
mMediaItemList.add(0, new MediaItem(device));
+ mMediaItemList.add(1, new MediaItem(mContext.getString(
+ R.string.media_output_group_title_speakers_and_displays),
+ MediaItem.MediaItemType.TYPE_GROUP_DIVIDER));
} else {
mMediaItemList.add(new MediaItem(device));
}
}
+ mMediaItemList.add(new MediaItem());
} else {
mMediaItemList.addAll(
devices.stream().map(MediaItem::new).collect(Collectors.toList()));
+ categorizeMediaItems(null);
}
-
- categorizeMediaItems();
return;
}
// selected device exist
@@ -666,11 +673,12 @@
mMediaItemList.add(new MediaItem(device));
}
}
- categorizeMediaItems();
+ categorizeMediaItems(connectedMediaDevice);
return;
}
// To keep the same list order
final List<MediaDevice> targetMediaDevices = new ArrayList<>();
+ final Map<Integer, MediaItem> dividerItems = new HashMap<>();
for (MediaItem originalMediaItem : mMediaItemList) {
for (MediaDevice newDevice : devices) {
if (originalMediaItem.getMediaDevice().isPresent()
@@ -680,6 +688,10 @@
break;
}
}
+ if (originalMediaItem.getMediaItemType()
+ == MediaItem.MediaItemType.TYPE_GROUP_DIVIDER) {
+ dividerItems.put(mMediaItemList.indexOf(originalMediaItem), originalMediaItem);
+ }
}
if (targetMediaDevices.size() != devices.size()) {
devices.removeAll(targetMediaDevices);
@@ -688,13 +700,34 @@
mMediaItemList.clear();
mMediaItemList.addAll(
targetMediaDevices.stream().map(MediaItem::new).collect(Collectors.toList()));
- categorizeMediaItems();
+ dividerItems.forEach((key, item) -> {
+ mMediaItemList.add(key, item);
+ });
+ mMediaItemList.add(new MediaItem());
}
}
- private void categorizeMediaItems() {
+ private void categorizeMediaItems(MediaDevice connectedMediaDevice) {
synchronized (mMediaDevicesLock) {
- //TODO(255124239): do the categorization here
+ Set<String> selectedDevicesIds = getSelectedMediaDevice().stream().map(
+ MediaDevice::getId).collect(Collectors.toSet());
+ if (connectedMediaDevice != null) {
+ selectedDevicesIds.add(connectedMediaDevice.getId());
+ }
+ int latestSelected = 1;
+ for (MediaItem item : mMediaItemList) {
+ if (item.getMediaDevice().isPresent()) {
+ MediaDevice device = item.getMediaDevice().get();
+ if (selectedDevicesIds.contains(device.getId())) {
+ latestSelected = mMediaItemList.indexOf(item) + 1;
+ } else {
+ mMediaItemList.add(latestSelected, new MediaItem(mContext.getString(
+ R.string.media_output_group_title_speakers_and_displays),
+ MediaItem.MediaItemType.TYPE_GROUP_DIVIDER));
+ break;
+ }
+ }
+ }
mMediaItemList.add(new MediaItem());
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/external/CustomTile.java b/packages/SystemUI/src/com/android/systemui/qs/external/CustomTile.java
index c4386ab..cfda9fd 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/external/CustomTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/external/CustomTile.java
@@ -18,6 +18,7 @@
import static android.view.Display.DEFAULT_DISPLAY;
import static android.view.WindowManager.LayoutParams.TYPE_QS_DIALOG;
+import android.app.PendingIntent;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
@@ -51,6 +52,7 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
+import com.android.systemui.animation.ActivityLaunchAnimator;
import com.android.systemui.dagger.qualifiers.Background;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.plugins.ActivityStarter;
@@ -92,6 +94,8 @@
private android.graphics.drawable.Icon mDefaultIcon;
@Nullable
private CharSequence mDefaultLabel;
+ @Nullable
+ private View mViewClicked;
private final Context mUserContext;
@@ -202,7 +206,7 @@
* Compare two icons, only works for resources.
*/
private boolean iconEquals(@Nullable android.graphics.drawable.Icon icon1,
- @Nullable android.graphics.drawable.Icon icon2) {
+ @Nullable android.graphics.drawable.Icon icon2) {
if (icon1 == icon2) {
return true;
}
@@ -229,7 +233,7 @@
/**
* Custom tile is considered available if there is a default icon (obtained from PM).
- *
+ * <p>
* It will return {@code true} before initialization, so tiles are not destroyed prematurely.
*/
@Override
@@ -262,6 +266,7 @@
/**
* Update state of {@link this#mTile} from a remote {@link TileService}.
+ *
* @param tile tile populated with state to apply
*/
public void updateTileState(Tile tile) {
@@ -293,6 +298,7 @@
if (tile.getStateDescription() != null || overwriteNulls) {
mTile.setStateDescription(tile.getStateDescription());
}
+ mTile.setActivityLaunchForClick(tile.getActivityLaunchForClick());
mTile.setState(tile.getState());
}
@@ -324,6 +330,7 @@
mService.onStartListening();
}
} else {
+ mViewClicked = null;
mService.onStopListening();
if (mIsTokenGranted && !mIsShowingDialog) {
try {
@@ -388,6 +395,7 @@
if (mTile.getState() == Tile.STATE_UNAVAILABLE) {
return;
}
+ mViewClicked = view;
try {
if (DEBUG) Log.d(TAG, "Adding token");
mWindowManager.addWindowToken(mToken, TYPE_QS_DIALOG, DEFAULT_DISPLAY,
@@ -400,7 +408,12 @@
mServiceManager.setBindRequested(true);
mService.onStartListening();
}
- mService.onClick(mToken);
+
+ if (mTile.getActivityLaunchForClick() != null) {
+ startActivityAndCollapse(mTile.getActivityLaunchForClick());
+ } else {
+ mService.onClick(mToken);
+ }
} catch (RemoteException e) {
// Called through wrapper, won't happen here.
}
@@ -483,6 +496,27 @@
});
}
+ /**
+ * Starts an {@link android.app.Activity}
+ * @param pendingIntent A PendingIntent for an Activity to be launched immediately.
+ */
+ public void startActivityAndCollapse(PendingIntent pendingIntent) {
+ if (!pendingIntent.isActivity()) {
+ Log.i(TAG, "Intent not for activity.");
+ } else if (!mIsTokenGranted) {
+ Log.i(TAG, "Launching activity before click");
+ } else {
+ Log.i(TAG, "The activity is starting");
+ ActivityLaunchAnimator.Controller controller = mViewClicked == null
+ ? null
+ : ActivityLaunchAnimator.Controller.fromView(mViewClicked, 0);
+ mUiHandler.post(() ->
+ mActivityStarter.startPendingIntentDismissingKeyguard(
+ pendingIntent, null, controller)
+ );
+ }
+ }
+
public static String toSpec(ComponentName name) {
return PREFIX + name.flattenToShortString() + ")";
}
@@ -509,8 +543,8 @@
/**
* Create a {@link CustomTile} for a given spec and user.
*
- * @param builder including injected common dependencies.
- * @param spec as provided by {@link CustomTile#toSpec}
+ * @param builder including injected common dependencies.
+ * @param spec as provided by {@link CustomTile#toSpec}
* @param userContext context for the user that is creating this tile.
* @return a new {@link CustomTile}
*/
diff --git a/packages/SystemUI/src/com/android/systemui/qs/external/TileServices.java b/packages/SystemUI/src/com/android/systemui/qs/external/TileServices.java
index 5d03da3..3d48fd1 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/external/TileServices.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/external/TileServices.java
@@ -15,6 +15,7 @@
*/
package com.android.systemui.qs.external;
+import android.app.PendingIntent;
import android.content.ComponentName;
import android.content.Context;
import android.content.pm.PackageInfo;
@@ -32,6 +33,7 @@
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
+import androidx.annotation.VisibleForTesting;
import com.android.internal.statusbar.StatusBarIcon;
import com.android.systemui.broadcast.BroadcastDispatcher;
@@ -276,6 +278,19 @@
}
@Override
+ public void startActivity(IBinder token, PendingIntent pendingIntent) {
+ startActivity(getTileForToken(token), pendingIntent);
+ }
+
+ @VisibleForTesting
+ protected void startActivity(CustomTile customTile, PendingIntent pendingIntent) {
+ if (customTile != null) {
+ verifyCaller(customTile);
+ customTile.startActivityAndCollapse(pendingIntent);
+ }
+ }
+
+ @Override
public void updateStatusIcon(IBinder token, Icon icon, String contentDescription) {
CustomTile customTile = getTileForToken(token);
if (customTile != null) {
@@ -336,7 +351,7 @@
}
@Nullable
- private CustomTile getTileForToken(IBinder token) {
+ public CustomTile getTileForToken(IBinder token) {
synchronized (mServices) {
return mTokenMap.get(token);
}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
index eabd276..1709043 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
@@ -634,6 +634,7 @@
private final KeyguardBottomAreaViewModel mKeyguardBottomAreaViewModel;
private final KeyguardBottomAreaInteractor mKeyguardBottomAreaInteractor;
private float mMinExpandHeight;
+ private ShadeHeightLogger mShadeHeightLogger;
private boolean mPanelUpdateWhenAnimatorEnds;
private boolean mHasVibratedOnOpen = false;
private int mFixedDuration = NO_FIXED_DURATION;
@@ -713,6 +714,7 @@
KeyguardUpdateMonitor keyguardUpdateMonitor,
MetricsLogger metricsLogger,
ShadeLogger shadeLogger,
+ ShadeHeightLogger shadeHeightLogger,
ConfigurationController configurationController,
Provider<FlingAnimationUtils.Builder> flingAnimationUtilsBuilder,
StatusBarTouchableRegionManager statusBarTouchableRegionManager,
@@ -774,6 +776,7 @@
mLockscreenGestureLogger = lockscreenGestureLogger;
mShadeExpansionStateManager = shadeExpansionStateManager;
mShadeLog = shadeLogger;
+ mShadeHeightLogger = shadeHeightLogger;
mGutsManager = gutsManager;
mView.addOnAttachStateChangeListener(new View.OnAttachStateChangeListener() {
@Override
@@ -1819,6 +1822,7 @@
waiting = true;
} else {
resetViews(false /* animate */);
+ mShadeHeightLogger.logFunctionCall("collapsePanel");
setExpandedFraction(0); // just in case
}
if (!waiting) {
@@ -3695,6 +3699,7 @@
beginJankMonitoring();
fling(0 /* expand */);
} else {
+ mShadeHeightLogger.logFunctionCall("expand");
setExpandedFraction(1f);
}
mInstantExpanding = false;
@@ -4759,6 +4764,7 @@
mInitialTouchFromKeyguard = mKeyguardStateController.isShowing();
if (startTracking) {
mTouchSlopExceeded = true;
+ mShadeHeightLogger.logFunctionCall("startExpandMotion");
setExpandedHeight(mInitialOffsetOnTouch);
onTrackingStarted();
}
@@ -4904,6 +4910,7 @@
@VisibleForTesting
void setExpandedHeight(float height) {
debugLog("setExpandedHeight(%.1f)", height);
+ mShadeHeightLogger.logFunctionCall("setExpandedHeight");
setExpandedHeightInternal(height);
}
@@ -4927,10 +4934,13 @@
return;
}
+ mShadeHeightLogger.logFunctionCall("updateExpandedHeightToMaxHeight");
setExpandedHeight(currentMaxPanelHeight);
}
private void setExpandedHeightInternal(float h) {
+ mShadeHeightLogger.logSetExpandedHeightInternal(h, mSystemClock.currentTimeMillis());
+
if (isNaN(h)) {
Log.wtf(TAG, "ExpandedHeight set to NaN");
}
@@ -4987,7 +4997,9 @@
/** Sets the expanded height relative to a number from 0 to 1. */
public void setExpandedFraction(float frac) {
- setExpandedHeight(getMaxPanelTransitionDistance() * frac);
+ final int maxDist = getMaxPanelTransitionDistance();
+ mShadeHeightLogger.logFunctionCall("setExpandedFraction");
+ setExpandedHeight(maxDist * frac);
}
float getExpandedHeight() {
@@ -5049,6 +5061,7 @@
/** Collapses the shade instantly without animation. */
public void instantCollapse() {
abortAnimations();
+ mShadeHeightLogger.logFunctionCall("instantCollapse");
setExpandedFraction(0f);
if (mExpanding) {
notifyExpandingFinished();
@@ -5165,6 +5178,7 @@
animator.getAnimatedFraction()));
setOverExpansionInternal(expansion, false /* isFromGesture */);
}
+ mShadeHeightLogger.logFunctionCall("height animator update");
setExpandedHeightInternal((float) animation.getAnimatedValue());
});
return animator;
@@ -5639,6 +5653,7 @@
mStatusBarStateController.setUpcomingState(KEYGUARD);
mStatusBarStateListener.onStateChanged(KEYGUARD);
mStatusBarStateListener.onDozeAmountChanged(1f, 1f);
+ mShadeHeightLogger.logFunctionCall("showAodUi");
setExpandedFraction(1f);
}
@@ -6185,6 +6200,7 @@
// otherwise {@link NotificationStackScrollLayout}
// wrongly enables stack height updates at the start of lockscreen swipe-up
mAmbientState.setSwipingUp(h <= 0);
+ mShadeHeightLogger.logFunctionCall("ACTION_MOVE");
setExpandedHeightInternal(newHeight);
}
break;
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeHeightLogger.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeHeightLogger.kt
new file mode 100644
index 0000000..e610b98
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeHeightLogger.kt
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2022 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.shade
+
+import com.android.systemui.log.dagger.ShadeHeightLog
+import com.android.systemui.plugins.log.LogBuffer
+import com.android.systemui.plugins.log.LogLevel.DEBUG
+import java.text.SimpleDateFormat
+import javax.inject.Inject
+
+private const val TAG = "ShadeHeightLogger"
+
+/**
+ * Log the call stack for [NotificationPanelViewController] setExpandedHeightInternal.
+ *
+ * Tracking bug: b/261593829
+ */
+class ShadeHeightLogger
+@Inject constructor(
+ @ShadeHeightLog private val buffer: LogBuffer,
+) {
+
+ private val dateFormat = SimpleDateFormat("yyyy-MM-dd-HH-mm-ss-SSS")
+
+ fun logFunctionCall(functionName: String) {
+ buffer.log(TAG, DEBUG, {
+ str1 = functionName
+ }, {
+ "$str1"
+ })
+ }
+
+ fun logSetExpandedHeightInternal(h: Float, time: Long) {
+ buffer.log(TAG, DEBUG, {
+ double1 = h.toDouble()
+ long1 = time
+ }, {
+ "setExpandedHeightInternal=$double1 time=${dateFormat.format(long1)}"
+ })
+ }
+}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/fsi/FsiChromeViewModelFactory.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/fsi/FsiChromeViewModelFactory.kt
new file mode 100644
index 0000000..1ca698b
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/fsi/FsiChromeViewModelFactory.kt
@@ -0,0 +1,87 @@
+package com.android.systemui.statusbar.notification.fsi
+
+import android.annotation.UiContext
+import android.app.PendingIntent
+import android.content.Context
+import android.graphics.drawable.Drawable
+import com.android.systemui.CoreStartable
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.statusbar.notification.fsi.FsiDebug.Companion.log
+import com.android.wm.shell.TaskView
+import com.android.wm.shell.TaskViewFactory
+import java.util.Optional
+import java.util.concurrent.Executor
+import javax.inject.Inject
+import kotlin.coroutines.resume
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.mapLatest
+import kotlinx.coroutines.suspendCancellableCoroutine
+
+/**
+ * Handle view-related data for fullscreen intent container on lockscreen. Wraps FsiChromeRepo,
+ * transforms events/state into view-relevant representation for FsiChromeView. Alive for lifetime
+ * of SystemUI.
+ */
+@SysUISingleton
+class FsiChromeViewModelFactory
+@Inject
+constructor(
+ val repo: FsiChromeRepo,
+ val taskViewFactory: Optional<TaskViewFactory>,
+ @UiContext val context: Context,
+ @Main val mainExecutor: Executor,
+) : CoreStartable {
+
+ companion object {
+ private const val classTag = "FsiChromeViewModelFactory"
+ }
+
+ val viewModelFlow: Flow<FsiChromeViewModel?> =
+ repo.infoFlow.mapLatest { fsiInfo ->
+ fsiInfo?.let {
+ log("$classTag viewModelFlow got new fsiInfo")
+
+ // mapLatest emits null when FSIInfo is null
+ FsiChromeViewModel(
+ fsiInfo.appName,
+ fsiInfo.appIcon,
+ createTaskView(),
+ fsiInfo.fullscreenIntent,
+ repo
+ )
+ }
+ }
+
+ override fun start() {
+ log("$classTag start")
+ }
+
+ private suspend fun createTaskView(): TaskView = suspendCancellableCoroutine { k ->
+ log("$classTag createTaskView")
+
+ taskViewFactory.get().create(context, mainExecutor) { taskView -> k.resume(taskView) }
+ }
+}
+
+// Alive for lifetime of FSI.
+data class FsiChromeViewModel(
+ val appName: String,
+ val appIcon: Drawable,
+ val taskView: TaskView,
+ val fsi: PendingIntent,
+ val repo: FsiChromeRepo
+) {
+ companion object {
+ private const val classTag = "FsiChromeViewModel"
+ }
+
+ fun onDismiss() {
+ log("$classTag onDismiss")
+ repo.dismiss()
+ }
+ fun onFullscreen() {
+ log("$classTag onFullscreen")
+ repo.onFullscreen()
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconAreaController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconAreaController.java
index e70c81d..85590fc 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconAreaController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconAreaController.java
@@ -11,6 +11,7 @@
import android.view.View;
import android.widget.FrameLayout;
+import androidx.annotation.ColorInt;
import androidx.annotation.NonNull;
import androidx.annotation.VisibleForTesting;
import androidx.collection.ArrayMap;
@@ -62,6 +63,8 @@
public static final String HIGH_PRIORITY = "high_priority";
private static final long AOD_ICONS_APPEAR_DURATION = 200;
+ @ColorInt
+ private static final int DEFAULT_AOD_ICON_COLOR = 0xffffffff;
private final ContrastColorUtil mContrastColorUtil;
private final Runnable mUpdateStatusBarIcons = this::updateStatusBarIcons;
@@ -84,7 +87,7 @@
private NotificationIconContainer mShelfIcons;
private NotificationIconContainer mAodIcons;
private final ArrayList<Rect> mTintAreas = new ArrayList<>();
- private Context mContext;
+ private final Context mContext;
private final DemoModeController mDemoModeController;
@@ -567,7 +570,7 @@
private void reloadAodColor() {
mAodIconTint = Utils.getColorAttrDefaultColor(mContext,
- R.attr.wallpaperTextColor);
+ R.attr.wallpaperTextColor, DEFAULT_AOD_ICON_COLOR);
}
private void updateAodIconColors() {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionsRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionsRepository.kt
index aea85eb..498c0b9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionsRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionsRepository.kt
@@ -17,8 +17,11 @@
package com.android.systemui.statusbar.pipeline.mobile.data.repository
import android.provider.Settings
+import android.telephony.CarrierConfigManager
import android.telephony.SubscriptionManager
import com.android.settingslib.SignalIcon.MobileIconGroup
+import com.android.settingslib.mobile.MobileMappings
+import com.android.settingslib.mobile.MobileMappings.Config
import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileConnectivityModel
import com.android.systemui.statusbar.pipeline.mobile.data.model.SubscriptionModel
import kotlinx.coroutines.flow.Flow
@@ -47,6 +50,18 @@
/** Observe changes to the [Settings.Global.MOBILE_DATA] setting */
val globalMobileDataSettingChangedEvent: Flow<Unit>
+ /**
+ * [Config] is an object that tracks relevant configuration flags for a given subscription ID.
+ * In the case of [MobileMappings], it's hard-coded to check the default data subscription's
+ * config, so this will apply to every icon that we care about.
+ *
+ * Relevant bits in the config are things like
+ * [CarrierConfigManager.KEY_SHOW_4G_FOR_LTE_DATA_ICON_BOOL]
+ *
+ * This flow will produce whenever the default data subscription or the carrier config changes.
+ */
+ val defaultDataSubRatConfig: StateFlow<Config>
+
/** The icon mapping from network type to [MobileIconGroup] for the default subscription */
val defaultMobileIconMapping: Flow<Map<String, MobileIconGroup>>
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileRepositorySwitcher.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileRepositorySwitcher.kt
index d8e0e81..db9d24f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileRepositorySwitcher.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileRepositorySwitcher.kt
@@ -19,6 +19,7 @@
import android.os.Bundle
import androidx.annotation.VisibleForTesting
import com.android.settingslib.SignalIcon
+import com.android.settingslib.mobile.MobileMappings
import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.demomode.DemoMode
@@ -123,6 +124,15 @@
realRepository.activeMobileDataSubscriptionId.value
)
+ override val defaultDataSubRatConfig: StateFlow<MobileMappings.Config> =
+ activeRepo
+ .flatMapLatest { it.defaultDataSubRatConfig }
+ .stateIn(
+ scope,
+ SharingStarted.WhileSubscribed(),
+ realRepository.defaultDataSubRatConfig.value
+ )
+
override val defaultMobileIconMapping: Flow<Map<String, SignalIcon.MobileIconGroup>> =
activeRepo.flatMapLatest { it.defaultMobileIconMapping }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionsRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionsRepository.kt
index 1e7fae7..1c08525 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionsRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionsRepository.kt
@@ -106,7 +106,8 @@
)
/** Demo mode doesn't currently support modifications to the mobile mappings */
- val defaultDataSubRatConfig = MutableStateFlow(MobileMappings.Config.readConfig(context))
+ override val defaultDataSubRatConfig =
+ MutableStateFlow(MobileMappings.Config.readConfig(context))
override val defaultMobileIconGroup = flowOf(TelephonyIcons.THREE_G)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryImpl.kt
index f27a9c9..483df47 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryImpl.kt
@@ -37,7 +37,6 @@
import androidx.annotation.VisibleForTesting
import com.android.internal.telephony.PhoneConstants
import com.android.settingslib.SignalIcon.MobileIconGroup
-import com.android.settingslib.mobile.MobileMappings
import com.android.settingslib.mobile.MobileMappings.Config
import com.android.systemui.broadcast.BroadcastDispatcher
import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
@@ -152,17 +151,7 @@
IntentFilter(CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED)
)
- /**
- * [Config] is an object that tracks relevant configuration flags for a given subscription ID.
- * In the case of [MobileMappings], it's hard-coded to check the default data subscription's
- * config, so this will apply to every icon that we care about.
- *
- * Relevant bits in the config are things like
- * [CarrierConfigManager.KEY_SHOW_4G_FOR_LTE_DATA_ICON_BOOL]
- *
- * This flow will produce whenever the default data subscription or the carrier config changes.
- */
- private val defaultDataSubRatConfig: StateFlow<Config> =
+ override val defaultDataSubRatConfig: StateFlow<Config> =
merge(defaultDataSubIdChangeEvent, carrierConfigChangedEvent)
.mapLatest { Config.readConfig(context) }
.stateIn(
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractor.kt
index 8e1197c..a26f28a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractor.kt
@@ -45,6 +45,9 @@
/** Observable for the data enabled state of this connection */
val isDataEnabled: StateFlow<Boolean>
+ /** True if the RAT icon should always be displayed and false otherwise. */
+ val alwaysShowDataRatIcon: StateFlow<Boolean>
+
/** Observable for RAT type (network type) indicator */
val networkTypeIconGroup: StateFlow<MobileIconGroup>
@@ -64,6 +67,7 @@
class MobileIconInteractorImpl(
@Application scope: CoroutineScope,
defaultSubscriptionHasDataEnabled: StateFlow<Boolean>,
+ override val alwaysShowDataRatIcon: StateFlow<Boolean>,
defaultMobileIconMapping: StateFlow<Map<String, MobileIconGroup>>,
defaultMobileIconGroup: StateFlow<MobileIconGroup>,
override val isDefaultConnectionFailed: StateFlow<Boolean>,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractor.kt
index 6f8fb2e..21f6d8e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractor.kt
@@ -55,6 +55,8 @@
val filteredSubscriptions: Flow<List<SubscriptionModel>>
/** True if the active mobile data subscription has data enabled */
val activeDataConnectionHasDataEnabled: StateFlow<Boolean>
+ /** True if the RAT icon should always be displayed and false otherwise. */
+ val alwaysShowDataRatIcon: StateFlow<Boolean>
/** The icon mapping from network type to [MobileIconGroup] for the default subscription */
val defaultMobileIconMapping: StateFlow<Map<String, MobileIconGroup>>
/** Fallback [MobileIconGroup] in the case where there is no icon in the mapping */
@@ -158,6 +160,11 @@
initialValue = mapOf()
)
+ override val alwaysShowDataRatIcon: StateFlow<Boolean> =
+ mobileConnectionsRepo.defaultDataSubRatConfig
+ .mapLatest { it.alwaysShowDataRatIcon }
+ .stateIn(scope, SharingStarted.WhileSubscribed(), false)
+
/** If there is no mapping in [defaultMobileIconMapping], then use this default icon group */
override val defaultMobileIconGroup: StateFlow<MobileIconGroup> =
mobileConnectionsRepo.defaultMobileIconGroup.stateIn(
@@ -188,6 +195,7 @@
MobileIconInteractorImpl(
scope,
activeDataConnectionHasDataEnabled,
+ alwaysShowDataRatIcon,
defaultMobileIconMapping,
defaultMobileIconGroup,
isDefaultConnectionFailed,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModel.kt
index 7869021..8ebd718 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModel.kt
@@ -71,15 +71,19 @@
iconInteractor.isDataConnected,
iconInteractor.isDataEnabled,
iconInteractor.isDefaultConnectionFailed,
- ) { networkTypeIconGroup, dataConnected, dataEnabled, failedConnection ->
- if (!dataConnected || !dataEnabled || failedConnection) {
- null
- } else {
- val desc =
- if (networkTypeIconGroup.dataContentDescription != 0)
- ContentDescription.Resource(networkTypeIconGroup.dataContentDescription)
- else null
- Icon.Resource(networkTypeIconGroup.dataType, desc)
+ iconInteractor.alwaysShowDataRatIcon,
+ ) { networkTypeIconGroup, dataConnected, dataEnabled, failedConnection, alwaysShow ->
+ val desc =
+ if (networkTypeIconGroup.dataContentDescription != 0)
+ ContentDescription.Resource(networkTypeIconGroup.dataContentDescription)
+ else null
+ val icon = Icon.Resource(networkTypeIconGroup.dataType, desc)
+ return@combine when {
+ alwaysShow -> icon
+ !dataConnected -> null
+ !dataEnabled -> null
+ failedConnection -> null
+ else -> icon
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/unfold/UnfoldLatencyTracker.kt b/packages/SystemUI/src/com/android/systemui/unfold/UnfoldLatencyTracker.kt
index 79b42b8..9269df3 100644
--- a/packages/SystemUI/src/com/android/systemui/unfold/UnfoldLatencyTracker.kt
+++ b/packages/SystemUI/src/com/android/systemui/unfold/UnfoldLatencyTracker.kt
@@ -16,12 +16,18 @@
package com.android.systemui.unfold
+import android.content.ContentResolver
import android.content.Context
import android.hardware.devicestate.DeviceStateManager
+import android.util.Log
import com.android.internal.util.LatencyTracker
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.UiBackground
import com.android.systemui.keyguard.ScreenLifecycle
+import com.android.systemui.unfold.UnfoldTransitionProgressProvider.TransitionProgressListener
+import com.android.systemui.unfold.util.ScaleAwareTransitionProgressProvider.Companion.areAnimationsEnabled
+import com.android.systemui.util.Compile
+import java.util.Optional
import java.util.concurrent.Executor
import javax.inject.Inject
@@ -41,17 +47,19 @@
constructor(
private val latencyTracker: LatencyTracker,
private val deviceStateManager: DeviceStateManager,
+ private val transitionProgressProvider: Optional<UnfoldTransitionProgressProvider>,
@UiBackground private val uiBgExecutor: Executor,
private val context: Context,
+ private val contentResolver: ContentResolver,
private val screenLifecycle: ScreenLifecycle
-) : ScreenLifecycle.Observer {
+) : ScreenLifecycle.Observer, TransitionProgressListener {
private var folded: Boolean? = null
+ private var isTransitionEnabled: Boolean? = null
private val foldStateListener = FoldStateListener(context)
private val isFoldable: Boolean
get() =
- context
- .resources
+ context.resources
.getIntArray(com.android.internal.R.array.config_foldedDeviceStates)
.isNotEmpty()
@@ -62,6 +70,11 @@
}
deviceStateManager.registerCallback(uiBgExecutor, foldStateListener)
screenLifecycle.addObserver(this)
+ if (transitionProgressProvider.isPresent) {
+ // Might not be present if the device is not a foldable device or unfold transition
+ // is disabled in the device configuration
+ transitionProgressProvider.get().addCallback(this)
+ }
}
/**
@@ -71,16 +84,72 @@
* end action event only if we previously received a fold state.
*/
override fun onScreenTurnedOn() {
- if (folded == false) {
+ if (DEBUG) {
+ Log.d(
+ TAG,
+ "onScreenTurnedOn: folded = $folded, isTransitionEnabled = $isTransitionEnabled"
+ )
+ }
+
+ // We use onScreenTurnedOn event to finish tracking only if we are not playing
+ // the unfold animation (e.g. it could be disabled because of battery saver).
+ // When animation is enabled finishing of the tracking will be done in onTransitionStarted.
+ if (folded == false && isTransitionEnabled == false) {
latencyTracker.onActionEnd(LatencyTracker.ACTION_SWITCH_DISPLAY_UNFOLD)
+
+ if (DEBUG) {
+ Log.d(TAG, "onScreenTurnedOn: ending ACTION_SWITCH_DISPLAY_UNFOLD")
+ }
+ }
+ }
+
+ /**
+ * This callback is used to end the metric when the unfold animation is enabled because it could
+ * add an additional delay to synchronize with launcher.
+ */
+ override fun onTransitionStarted() {
+ if (DEBUG) {
+ Log.d(
+ TAG,
+ "onTransitionStarted: folded = $folded, isTransitionEnabled = $isTransitionEnabled"
+ )
+ }
+
+ if (folded == false && isTransitionEnabled == true) {
+ latencyTracker.onActionEnd(LatencyTracker.ACTION_SWITCH_DISPLAY_UNFOLD)
+
+ if (DEBUG) {
+ Log.d(TAG, "onTransitionStarted: ending ACTION_SWITCH_DISPLAY_UNFOLD")
+ }
}
}
private fun onFoldEvent(folded: Boolean) {
- if (this.folded != folded) {
+ val oldFolded = this.folded
+
+ if (oldFolded != folded) {
this.folded = folded
- if (!folded) { // unfolding started
+
+ if (DEBUG) {
+ Log.d(TAG, "Received onFoldEvent = $folded")
+ }
+
+ // Do not start tracking when oldFolded is null, this means that this is the first
+ // onFoldEvent after booting the device or starting SystemUI and not actual folding or
+ // unfolding the device.
+ if (oldFolded != null && !folded) {
+ // Unfolding started
latencyTracker.onActionStart(LatencyTracker.ACTION_SWITCH_DISPLAY_UNFOLD)
+ isTransitionEnabled =
+ transitionProgressProvider.isPresent && contentResolver.areAnimationsEnabled()
+
+ if (DEBUG) {
+ Log.d(
+ TAG,
+ "Starting ACTION_SWITCH_DISPLAY_UNFOLD, " +
+ "isTransitionEnabled = $isTransitionEnabled"
+ )
+ }
}
}
}
@@ -88,3 +157,6 @@
private inner class FoldStateListener(context: Context) :
DeviceStateManager.FoldStateListener(context, { onFoldEvent(it) })
}
+
+private const val TAG = "UnfoldLatencyTracker"
+private val DEBUG = Compile.IS_DEBUG && Log.isLoggable(TAG, Log.VERBOSE)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputControllerTest.java
index 71c300c..b16a39f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputControllerTest.java
@@ -102,6 +102,8 @@
private MediaOutputController.Callback mCb = mock(MediaOutputController.Callback.class);
private MediaDevice mMediaDevice1 = mock(MediaDevice.class);
private MediaDevice mMediaDevice2 = mock(MediaDevice.class);
+ private MediaItem mMediaItem1 = mock(MediaItem.class);
+ private MediaItem mMediaItem2 = mock(MediaItem.class);
private NearbyDevice mNearbyDevice1 = mock(NearbyDevice.class);
private NearbyDevice mNearbyDevice2 = mock(NearbyDevice.class);
private MediaMetadata mMediaMetadata = mock(MediaMetadata.class);
@@ -125,6 +127,7 @@
private LocalMediaManager mLocalMediaManager;
private List<MediaController> mMediaControllers = new ArrayList<>();
private List<MediaDevice> mMediaDevices = new ArrayList<>();
+ private List<MediaItem> mMediaItemList = new ArrayList<>();
private List<NearbyDevice> mNearbyDevices = new ArrayList<>();
private MediaDescription mMediaDescription;
private List<RoutingSessionInfo> mRoutingSessionInfos = new ArrayList<>();
@@ -157,6 +160,11 @@
when(mMediaDevice2.getId()).thenReturn(TEST_DEVICE_2_ID);
mMediaDevices.add(mMediaDevice1);
mMediaDevices.add(mMediaDevice2);
+ when(mMediaItem1.getMediaDevice()).thenReturn(Optional.of(mMediaDevice1));
+ when(mMediaItem2.getMediaDevice()).thenReturn(Optional.of(mMediaDevice2));
+ mMediaItemList.add(mMediaItem1);
+ mMediaItemList.add(mMediaItem2);
+
when(mNearbyDevice1.getMediaRoute2Id()).thenReturn(TEST_DEVICE_1_ID);
when(mNearbyDevice1.getRangeZone()).thenReturn(NearbyDevice.RANGE_CLOSE);
@@ -314,6 +322,18 @@
}
@Test
+ public void advanced_onDeviceListUpdate_isRefreshing_updatesNeedRefreshToTrue() {
+ when(mFlags.isEnabled(Flags.OUTPUT_SWITCHER_ADVANCED_LAYOUT)).thenReturn(true);
+ mMediaOutputController.start(mCb);
+ reset(mCb);
+ mMediaOutputController.mIsRefreshing = true;
+
+ mMediaOutputController.onDeviceListUpdate(mMediaDevices);
+
+ assertThat(mMediaOutputController.mNeedRefresh).isTrue();
+ }
+
+ @Test
public void cancelMuteAwaitConnection_cancelsWithMediaManager() {
when(mAudioManager.getMutingExpectedDevice()).thenReturn(mock(AudioDeviceAttributes.class));
mMediaOutputController.start(mCb);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/external/CustomTileTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/external/CustomTileTest.kt
index f3fcdbf..2bd068a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/external/CustomTileTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/external/CustomTileTest.kt
@@ -16,6 +16,7 @@
package com.android.systemui.qs.external
+import android.app.PendingIntent
import android.content.ComponentName
import android.content.Context
import android.content.pm.ApplicationInfo
@@ -30,8 +31,10 @@
import android.testing.AndroidTestingRunner
import android.testing.TestableLooper
import android.view.IWindowManager
+import android.view.View
import com.android.internal.logging.MetricsLogger
import com.android.systemui.SysuiTestCase
+import com.android.systemui.animation.ActivityLaunchAnimator
import com.android.systemui.classifier.FalsingManagerFake
import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.plugins.qs.QSTile
@@ -39,8 +42,11 @@
import com.android.systemui.qs.QSHost
import com.android.systemui.qs.logging.QSLogger
import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.eq
+import com.android.systemui.util.mockito.nullable
import org.junit.Assert.assertEquals
import org.junit.Assert.assertFalse
+import org.junit.Assert.assertThrows
import org.junit.Assert.assertTrue
import org.junit.Before
import org.junit.Test
@@ -236,6 +242,10 @@
`when`(tile.qsTile.icon.loadDrawable(any(Context::class.java)))
.thenReturn(mock(Drawable::class.java))
+ val pi = mock(PendingIntent::class.java)
+ `when`(pi.isActivity).thenReturn(true)
+ tile.qsTile.activityLaunchForClick = pi
+
tile.refreshState()
testableLooper.processAllMessages()
@@ -289,4 +299,52 @@
assertFalse(tile.isAvailable)
verify(tileHost).removeTile(tile.tileSpec)
}
+
+ @Test
+ fun testInvalidPendingIntentDoesNotStartActivity() {
+ val pi = mock(PendingIntent::class.java)
+ `when`(pi.isActivity).thenReturn(false)
+ val tile = CustomTile.create(customTileBuilder, TILE_SPEC, mContext)
+
+ assertThrows(IllegalArgumentException::class.java) {
+ tile.qsTile.activityLaunchForClick = pi
+ }
+
+ tile.handleClick(mock(View::class.java))
+ testableLooper.processAllMessages()
+
+ verify(activityStarter, never())
+ .startPendingIntentDismissingKeyguard(
+ any(), any(), any(ActivityLaunchAnimator.Controller::class.java))
+ }
+
+ @Test
+ fun testValidPendingIntentWithNoClickDoesNotStartActivity() {
+ val pi = mock(PendingIntent::class.java)
+ `when`(pi.isActivity).thenReturn(true)
+ val tile = CustomTile.create(customTileBuilder, TILE_SPEC, mContext)
+ tile.qsTile.activityLaunchForClick = pi
+
+ testableLooper.processAllMessages()
+
+ verify(activityStarter, never())
+ .startPendingIntentDismissingKeyguard(
+ any(), any(), any(ActivityLaunchAnimator.Controller::class.java))
+ }
+
+ @Test
+ fun testValidPendingIntentStartsActivity() {
+ val pi = mock(PendingIntent::class.java)
+ `when`(pi.isActivity).thenReturn(true)
+ val tile = CustomTile.create(customTileBuilder, TILE_SPEC, mContext)
+ tile.qsTile.activityLaunchForClick = pi
+
+ tile.handleClick(mock(View::class.java))
+
+ testableLooper.processAllMessages()
+
+ verify(activityStarter)
+ .startPendingIntentDismissingKeyguard(
+ eq(pi), nullable(), nullable<ActivityLaunchAnimator.Controller>())
+ }
}
\ No newline at end of file
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileServicesTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileServicesTest.java
index 25c95ef..172c87f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileServicesTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileServicesTest.java
@@ -26,6 +26,7 @@
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
+import android.app.PendingIntent;
import android.content.ComponentName;
import android.content.Intent;
import android.os.Handler;
@@ -58,6 +59,7 @@
import com.android.systemui.util.settings.SecureSettings;
import org.junit.After;
+import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -245,6 +247,32 @@
verify(manager.getTileService()).onStartListening();
}
+ @Test
+ public void testValidCustomTileStartsActivity() {
+ CustomTile tile = mock(CustomTile.class);
+ PendingIntent pi = mock(PendingIntent.class);
+ ComponentName componentName = mock(ComponentName.class);
+ when(tile.getComponent()).thenReturn(componentName);
+ when(componentName.getPackageName()).thenReturn(this.getContext().getPackageName());
+
+ mTileService.startActivity(tile, pi);
+
+ verify(tile).startActivityAndCollapse(pi);
+ }
+
+ @Test
+ public void testInvalidCustomTileDoesNotStartActivity() {
+ CustomTile tile = mock(CustomTile.class);
+ PendingIntent pi = mock(PendingIntent.class);
+ ComponentName componentName = mock(ComponentName.class);
+ when(tile.getComponent()).thenReturn(componentName);
+ when(componentName.getPackageName()).thenReturn("invalid.package.name");
+
+ Assert.assertThrows(SecurityException.class, () -> mTileService.startActivity(tile, pi));
+
+ verify(tile, never()).startActivityAndCollapse(pi);
+ }
+
private class TestTileServices extends TileServices {
TestTileServices(QSTileHost host, Provider<Handler> handlerProvider,
BroadcastDispatcher broadcastDispatcher, UserTracker userTracker,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java
index e3a6b29..4843c76 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java
@@ -220,6 +220,7 @@
@Mock private KeyguardStateController mKeyguardStateController;
@Mock private DozeLog mDozeLog;
@Mock private ShadeLogger mShadeLog;
+ @Mock private ShadeHeightLogger mShadeHeightLogger;
@Mock private CommandQueue mCommandQueue;
@Mock private VibratorHelper mVibratorHelper;
@Mock private LatencyTracker mLatencyTracker;
@@ -457,6 +458,7 @@
mLatencyTracker, mPowerManager, mAccessibilityManager, 0, mUpdateMonitor,
mMetricsLogger,
mShadeLog,
+ mShadeHeightLogger,
mConfigurationController,
() -> flingAnimationUtilsBuilder, mStatusBarTouchableRegionManager,
mConversationNotificationManager, mMediaHierarchyManager,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/fsi/FsiChromeViewModelFactoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/fsi/FsiChromeViewModelFactoryTest.kt
new file mode 100644
index 0000000..5cee9e3
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/fsi/FsiChromeViewModelFactoryTest.kt
@@ -0,0 +1,112 @@
+/*
+ * Copyright (C) 2022 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.notification.fsi
+
+import android.app.PendingIntent
+import android.graphics.drawable.Drawable
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper.RunWithLooper
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.util.concurrency.FakeExecutor
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.mockito.whenever
+import com.android.systemui.util.mockito.withArgCaptor
+import com.android.systemui.util.time.FakeSystemClock
+import com.android.wm.shell.TaskView
+import com.android.wm.shell.TaskViewFactory
+import com.google.common.truth.Truth.assertThat
+import java.util.Optional
+import java.util.function.Consumer
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.onStart
+import kotlinx.coroutines.flow.stateIn
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.Mockito
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+@RunWithLooper(setAsMainLooper = true)
+class FsiChromeViewModelFactoryTest : SysuiTestCase() {
+ @Mock private lateinit var taskViewFactoryOptional: Optional<TaskViewFactory>
+ @Mock private lateinit var taskViewFactory: TaskViewFactory
+ @Mock lateinit var taskView: TaskView
+
+ @Main var mainExecutor = FakeExecutor(FakeSystemClock())
+ lateinit var viewModelFactory: FsiChromeViewModelFactory
+
+ private val fakeInfoFlow = MutableStateFlow<FsiChromeRepo.FSIInfo?>(null)
+ private var fsiChromeRepo: FsiChromeRepo =
+ mock<FsiChromeRepo>().apply { whenever(infoFlow).thenReturn(fakeInfoFlow) }
+
+ private val appName = "appName"
+ private val appIcon: Drawable = context.getDrawable(com.android.systemui.R.drawable.ic_android)
+ private val fsi: PendingIntent = Mockito.mock(PendingIntent::class.java)
+ private val fsiInfo = FsiChromeRepo.FSIInfo(appName, appIcon, fsi)
+
+ @Before
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+
+ whenever(taskViewFactoryOptional.get()).thenReturn(taskViewFactory)
+
+ viewModelFactory =
+ FsiChromeViewModelFactory(fsiChromeRepo, taskViewFactoryOptional, context, mainExecutor)
+ }
+
+ @Test
+ fun testViewModelFlow_update_createsTaskView() {
+ runTest {
+ val latestViewModel =
+ viewModelFactory.viewModelFlow
+ .onStart { FsiDebug.log("viewModelFactory.viewModelFlow.onStart") }
+ .stateIn(
+ backgroundScope, // stateIn runs forever, don't count it as test coroutine
+ SharingStarted.Eagerly,
+ null
+ )
+ runCurrent() // Drain queued backgroundScope operations
+
+ // Test: emit the fake FSIInfo
+ fakeInfoFlow.emit(fsiInfo)
+ runCurrent()
+
+ val taskViewFactoryCallback: Consumer<TaskView> = withArgCaptor {
+ verify(taskViewFactory).create(any(), any(), capture())
+ }
+ taskViewFactoryCallback.accept(taskView) // this will call k.resume
+ runCurrent()
+
+ // Verify that the factory has produced a new ViewModel
+ // containing the relevant data from FsiInfo
+ val expectedViewModel =
+ FsiChromeViewModel(appName, appIcon, taskView, fsi, fsiChromeRepo)
+
+ assertThat(latestViewModel.value).isEqualTo(expectedViewModel)
+ }
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeMobileConnectionsRepository.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeMobileConnectionsRepository.kt
index d6af0e6..04d3cdd 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeMobileConnectionsRepository.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeMobileConnectionsRepository.kt
@@ -20,6 +20,7 @@
import android.telephony.TelephonyDisplayInfo
import android.telephony.TelephonyManager
import com.android.settingslib.SignalIcon
+import com.android.settingslib.mobile.MobileMappings
import com.android.settingslib.mobile.TelephonyIcons
import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileConnectivityModel
import com.android.systemui.statusbar.pipeline.mobile.data.model.SubscriptionModel
@@ -68,6 +69,8 @@
private val _globalMobileDataSettingChangedEvent = MutableStateFlow(Unit)
override val globalMobileDataSettingChangedEvent = _globalMobileDataSettingChangedEvent
+ override val defaultDataSubRatConfig = MutableStateFlow(MobileMappings.Config())
+
private val _defaultMobileIconMapping = MutableStateFlow(TEST_MAPPING)
override val defaultMobileIconMapping = _defaultMobileIconMapping
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryTest.kt
index 4b82b39..6d80acb 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryTest.kt
@@ -23,6 +23,7 @@
import android.net.NetworkCapabilities.NET_CAPABILITY_VALIDATED
import android.net.NetworkCapabilities.TRANSPORT_CELLULAR
import android.provider.Settings
+import android.telephony.CarrierConfigManager
import android.telephony.SubscriptionInfo
import android.telephony.SubscriptionManager
import android.telephony.TelephonyCallback
@@ -30,6 +31,8 @@
import android.telephony.TelephonyManager
import androidx.test.filters.SmallTest
import com.android.internal.telephony.PhoneConstants
+import com.android.settingslib.R
+import com.android.settingslib.mobile.MobileMappings
import com.android.systemui.SysuiTestCase
import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileConnectivityModel
import com.android.systemui.statusbar.pipeline.mobile.data.model.SubscriptionModel
@@ -50,6 +53,7 @@
import kotlinx.coroutines.runBlocking
import org.junit.After
import org.junit.Assert.assertThrows
+import org.junit.Assert.assertTrue
import org.junit.Before
import org.junit.Test
import org.mockito.ArgumentMatchers.anyInt
@@ -63,6 +67,7 @@
class MobileConnectionsRepositoryTest : SysuiTestCase() {
private lateinit var underTest: MobileConnectionsRepositoryImpl
+ private lateinit var connectionFactory: MobileConnectionRepositoryImpl.Factory
@Mock private lateinit var connectivityManager: ConnectivityManager
@Mock private lateinit var subscriptionManager: SubscriptionManager
@Mock private lateinit var telephonyManager: TelephonyManager
@@ -84,7 +89,7 @@
}
}
- val connectionFactory: MobileConnectionRepositoryImpl.Factory =
+ connectionFactory =
MobileConnectionRepositoryImpl.Factory(
context = context,
telephonyManager = telephonyManager,
@@ -385,6 +390,92 @@
job.cancel()
}
+ @Test
+ fun config_initiallyFromContext() =
+ runBlocking(IMMEDIATE) {
+ overrideResource(R.bool.config_showMin3G, true)
+ val configFromContext = MobileMappings.Config.readConfig(context)
+ assertThat(configFromContext.showAtLeast3G).isTrue()
+
+ // The initial value will be fetched when the repo is created, so we need to override
+ // the resources and then re-create the repo.
+ underTest =
+ MobileConnectionsRepositoryImpl(
+ connectivityManager,
+ subscriptionManager,
+ telephonyManager,
+ logger,
+ mobileMappings,
+ fakeBroadcastDispatcher,
+ globalSettings,
+ context,
+ IMMEDIATE,
+ scope,
+ connectionFactory,
+ )
+
+ var latest: MobileMappings.Config? = null
+ val job = underTest.defaultDataSubRatConfig.onEach { latest = it }.launchIn(this)
+
+ assertTrue(latest!!.areEqual(configFromContext))
+ assertTrue(latest!!.showAtLeast3G)
+
+ job.cancel()
+ }
+
+ @Test
+ fun config_subIdChangeEvent_updated() =
+ runBlocking(IMMEDIATE) {
+ var latest: MobileMappings.Config? = null
+ val job = underTest.defaultDataSubRatConfig.onEach { latest = it }.launchIn(this)
+ assertThat(latest!!.showAtLeast3G).isFalse()
+
+ overrideResource(R.bool.config_showMin3G, true)
+ val configFromContext = MobileMappings.Config.readConfig(context)
+ assertThat(configFromContext.showAtLeast3G).isTrue()
+
+ // WHEN the change event is fired
+ fakeBroadcastDispatcher.registeredReceivers.forEach { receiver ->
+ receiver.onReceive(
+ context,
+ Intent(TelephonyManager.ACTION_DEFAULT_DATA_SUBSCRIPTION_CHANGED)
+ .putExtra(PhoneConstants.SUBSCRIPTION_KEY, SUB_1_ID)
+ )
+ }
+
+ // THEN the config is updated
+ assertTrue(latest!!.areEqual(configFromContext))
+ assertTrue(latest!!.showAtLeast3G)
+
+ job.cancel()
+ }
+
+ @Test
+ fun config_carrierConfigChangeEvent_updated() =
+ runBlocking(IMMEDIATE) {
+ var latest: MobileMappings.Config? = null
+ val job = underTest.defaultDataSubRatConfig.onEach { latest = it }.launchIn(this)
+ assertThat(latest!!.showAtLeast3G).isFalse()
+
+ overrideResource(R.bool.config_showMin3G, true)
+ val configFromContext = MobileMappings.Config.readConfig(context)
+ assertThat(configFromContext.showAtLeast3G).isTrue()
+
+ // WHEN the change event is fired
+ fakeBroadcastDispatcher.registeredReceivers.forEach { receiver ->
+ receiver.onReceive(
+ context,
+ Intent(CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED)
+ )
+ }
+
+ // THEN the config is updated
+ assertThat(latest!!.areEqual(configFromContext)).isTrue()
+ assertThat(latest!!.showAtLeast3G).isTrue()
+
+ job.cancel()
+ }
+
private fun createCapabilities(connected: Boolean, validated: Boolean): NetworkCapabilities =
mock<NetworkCapabilities>().also {
whenever(it.hasTransport(TRANSPORT_CELLULAR)).thenReturn(connected)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/FakeMobileIconInteractor.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/FakeMobileIconInteractor.kt
index 3ae7d3c..1ff1636a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/FakeMobileIconInteractor.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/FakeMobileIconInteractor.kt
@@ -22,6 +22,8 @@
import kotlinx.coroutines.flow.MutableStateFlow
class FakeMobileIconInteractor : MobileIconInteractor {
+ override val alwaysShowDataRatIcon = MutableStateFlow(false)
+
private val _iconGroup = MutableStateFlow<SignalIcon.MobileIconGroup>(TelephonyIcons.THREE_G)
override val networkTypeIconGroup = _iconGroup
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/FakeMobileIconsInteractor.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/FakeMobileIconsInteractor.kt
index 0d4044d..9f300e9 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/FakeMobileIconsInteractor.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/FakeMobileIconsInteractor.kt
@@ -54,6 +54,8 @@
private val _activeDataConnectionHasDataEnabled = MutableStateFlow(false)
override val activeDataConnectionHasDataEnabled = _activeDataConnectionHasDataEnabled
+ override val alwaysShowDataRatIcon = MutableStateFlow(false)
+
private val _defaultMobileIconMapping = MutableStateFlow(TEST_MAPPING)
override val defaultMobileIconMapping = _defaultMobileIconMapping
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractorTest.kt
index fd41b5b..2281e89b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractorTest.kt
@@ -59,6 +59,7 @@
MobileIconInteractorImpl(
scope,
mobileIconsInteractor.activeDataConnectionHasDataEnabled,
+ mobileIconsInteractor.alwaysShowDataRatIcon,
mobileIconsInteractor.defaultMobileIconMapping,
mobileIconsInteractor.defaultMobileIconGroup,
mobileIconsInteractor.isDefaultConnectionFailed,
@@ -223,6 +224,21 @@
}
@Test
+ fun alwaysShowDataRatIcon_matchesParent() =
+ runBlocking(IMMEDIATE) {
+ var latest: Boolean? = null
+ val job = underTest.alwaysShowDataRatIcon.onEach { latest = it }.launchIn(this)
+
+ mobileIconsInteractor.alwaysShowDataRatIcon.value = true
+ assertThat(latest).isTrue()
+
+ mobileIconsInteractor.alwaysShowDataRatIcon.value = false
+ assertThat(latest).isFalse()
+
+ job.cancel()
+ }
+
+ @Test
fun test_isDefaultDataEnabled_matchesParent() =
runBlocking(IMMEDIATE) {
var latest: Boolean? = null
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractorTest.kt
index 58e57e2..8557894 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractorTest.kt
@@ -18,6 +18,7 @@
import android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID
import androidx.test.filters.SmallTest
+import com.android.settingslib.mobile.MobileMappings
import com.android.systemui.SysuiTestCase
import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileConnectivityModel
import com.android.systemui.statusbar.pipeline.mobile.data.model.SubscriptionModel
@@ -255,6 +256,38 @@
job.cancel()
}
+ @Test
+ fun alwaysShowDataRatIcon_configHasTrue() =
+ runBlocking(IMMEDIATE) {
+ var latest: Boolean? = null
+ val job = underTest.alwaysShowDataRatIcon.onEach { latest = it }.launchIn(this)
+
+ val config = MobileMappings.Config()
+ config.alwaysShowDataRatIcon = true
+ connectionsRepository.defaultDataSubRatConfig.value = config
+ yield()
+
+ assertThat(latest).isTrue()
+
+ job.cancel()
+ }
+
+ @Test
+ fun alwaysShowDataRatIcon_configHasFalse() =
+ runBlocking(IMMEDIATE) {
+ var latest: Boolean? = null
+ val job = underTest.alwaysShowDataRatIcon.onEach { latest = it }.launchIn(this)
+
+ val config = MobileMappings.Config()
+ config.alwaysShowDataRatIcon = false
+ connectionsRepository.defaultDataSubRatConfig.value = config
+ yield()
+
+ assertThat(latest).isFalse()
+
+ job.cancel()
+ }
+
companion object {
private val IMMEDIATE = Dispatchers.Main.immediate
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModelTest.kt
index d4c2c3f..f2533a9 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModelTest.kt
@@ -174,6 +174,66 @@
job.cancel()
}
+ @Test
+ fun networkType_alwaysShow_shownEvenWhenDisabled() =
+ runBlocking(IMMEDIATE) {
+ interactor.setIconGroup(THREE_G)
+ interactor.setIsDataEnabled(true)
+ interactor.alwaysShowDataRatIcon.value = true
+
+ var latest: Icon? = null
+ val job = underTest.networkTypeIcon.onEach { latest = it }.launchIn(this)
+
+ val expected =
+ Icon.Resource(
+ THREE_G.dataType,
+ ContentDescription.Resource(THREE_G.dataContentDescription)
+ )
+ assertThat(latest).isEqualTo(expected)
+
+ job.cancel()
+ }
+
+ @Test
+ fun networkType_alwaysShow_shownEvenWhenDisconnected() =
+ runBlocking(IMMEDIATE) {
+ interactor.setIconGroup(THREE_G)
+ interactor.isDataConnected.value = false
+ interactor.alwaysShowDataRatIcon.value = true
+
+ var latest: Icon? = null
+ val job = underTest.networkTypeIcon.onEach { latest = it }.launchIn(this)
+
+ val expected =
+ Icon.Resource(
+ THREE_G.dataType,
+ ContentDescription.Resource(THREE_G.dataContentDescription)
+ )
+ assertThat(latest).isEqualTo(expected)
+
+ job.cancel()
+ }
+
+ @Test
+ fun networkType_alwaysShow_shownEvenWhenFailedConnection() =
+ runBlocking(IMMEDIATE) {
+ interactor.setIconGroup(THREE_G)
+ interactor.setIsFailedConnection(true)
+ interactor.alwaysShowDataRatIcon.value = true
+
+ var latest: Icon? = null
+ val job = underTest.networkTypeIcon.onEach { latest = it }.launchIn(this)
+
+ val expected =
+ Icon.Resource(
+ THREE_G.dataType,
+ ContentDescription.Resource(THREE_G.dataContentDescription)
+ )
+ assertThat(latest).isEqualTo(expected)
+
+ job.cancel()
+ }
+
/** Convenience constructor for these tests */
private fun defaultSignal(
level: Int = 1,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/unfold/UnfoldLatencyTrackerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/unfold/UnfoldLatencyTrackerTest.kt
index 4e2736c7..c7dc0ab 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/unfold/UnfoldLatencyTrackerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/unfold/UnfoldLatencyTrackerTest.kt
@@ -18,6 +18,7 @@
import android.hardware.devicestate.DeviceStateManager
import android.hardware.devicestate.DeviceStateManager.FoldStateListener
+import android.provider.Settings
import android.testing.AndroidTestingRunner
import androidx.test.filters.SmallTest
import com.android.internal.util.LatencyTracker
@@ -32,9 +33,11 @@
import org.mockito.ArgumentCaptor
import org.mockito.Captor
import org.mockito.Mock
+import org.mockito.Mockito.never
import org.mockito.Mockito.verify
import org.mockito.Mockito.verifyNoMoreInteractions
import org.mockito.MockitoAnnotations
+import java.util.Optional
@RunWith(AndroidTestingRunner::class)
@SmallTest
@@ -59,14 +62,18 @@
private lateinit var unfoldLatencyTracker: UnfoldLatencyTracker
+ private val transitionProgressProvider = TestUnfoldTransitionProvider()
+
@Before
fun setUp() {
MockitoAnnotations.initMocks(this)
unfoldLatencyTracker = UnfoldLatencyTracker(
latencyTracker,
deviceStateManager,
+ Optional.of(transitionProgressProvider),
context.mainExecutor,
context,
+ context.contentResolver,
screenLifecycle
).apply { init() }
deviceStates = FoldableTestUtils.findDeviceStates(context)
@@ -76,8 +83,11 @@
}
@Test
- fun unfold_eventPropagated() {
+ fun unfold_startedFolded_animationsDisabled_eventPropagatedOnScreenTurnedOnEvent() {
+ setAnimationsEnabled(false)
+ sendFoldEvent(folded = true)
sendFoldEvent(folded = false)
+
sendScreenTurnedOnEvent()
verify(latencyTracker).onActionStart(any())
@@ -85,14 +95,77 @@
}
@Test
- fun fold_eventNotPropagated() {
+ fun unfold_startedFolded_animationsEnabledOnScreenTurnedOn_eventNotFinished() {
+ setAnimationsEnabled(true)
sendFoldEvent(folded = true)
+ sendFoldEvent(folded = false)
+
+ sendScreenTurnedOnEvent()
+
+ verify(latencyTracker).onActionStart(any())
+ verify(latencyTracker, never()).onActionEnd(any())
+ }
+
+ @Test
+ fun unfold_firstFoldEventAnimationsEnabledOnScreenTurnedOnAndTransitionStarted_eventNotPropagated() {
+ setAnimationsEnabled(true)
+ sendFoldEvent(folded = false)
+
+ sendScreenTurnedOnEvent()
+ transitionProgressProvider.onTransitionStarted()
+
+ verifyNoMoreInteractions(latencyTracker)
+ }
+
+ @Test
+ fun unfold_secondFoldEventAnimationsEnabledOnScreenTurnedOnAndTransitionStarted_eventPropagated() {
+ setAnimationsEnabled(true)
+ sendFoldEvent(folded = true)
+ sendFoldEvent(folded = false)
+
+ sendScreenTurnedOnEvent()
+ transitionProgressProvider.onTransitionStarted()
+
+ verify(latencyTracker).onActionStart(any())
+ verify(latencyTracker).onActionEnd(any())
+ }
+
+ @Test
+ fun unfold_unfoldFoldUnfoldAnimationsEnabledOnScreenTurnedOnAndTransitionStarted_eventPropagated() {
+ setAnimationsEnabled(true)
+ sendFoldEvent(folded = false)
+ sendFoldEvent(folded = true)
+ sendFoldEvent(folded = false)
+
+ sendScreenTurnedOnEvent()
+ transitionProgressProvider.onTransitionStarted()
+
+ verify(latencyTracker).onActionStart(any())
+ verify(latencyTracker).onActionEnd(any())
+ }
+
+ @Test
+ fun fold_animationsDisabled_screenTurnedOn_eventNotPropagated() {
+ setAnimationsEnabled(false)
+ sendFoldEvent(folded = true)
+
sendScreenTurnedOnEvent() // outer display on.
verifyNoMoreInteractions(latencyTracker)
}
@Test
+ fun fold_animationsEnabled_screenTurnedOn_eventNotPropagated() {
+ setAnimationsEnabled(true)
+ sendFoldEvent(folded = true)
+
+ sendScreenTurnedOnEvent() // outer display on.
+ transitionProgressProvider.onTransitionStarted()
+
+ verifyNoMoreInteractions(latencyTracker)
+ }
+
+ @Test
fun onScreenTurnedOn_stateNeverSet_eventNotPropagated() {
sendScreenTurnedOnEvent()
@@ -107,4 +180,20 @@
private fun sendScreenTurnedOnEvent() {
screenLifecycleCaptor.value.onScreenTurnedOn()
}
+
+ private fun setAnimationsEnabled(enabled: Boolean) {
+ val durationScale =
+ if (enabled) {
+ 1f
+ } else {
+ 0f
+ }
+
+ // It uses [TestableSettingsProvider] and it will be cleared after the test
+ Settings.Global.putString(
+ context.contentResolver,
+ Settings.Global.ANIMATOR_DURATION_SCALE,
+ durationScale.toString()
+ )
+ }
}
\ No newline at end of file
diff --git a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
index 2aed831..351e3bd 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
@@ -77,7 +77,6 @@
import android.view.ViewTreeObserver;
import android.view.WindowManager;
-import androidx.test.filters.FlakyTest;
import androidx.test.filters.SmallTest;
import com.android.internal.colorextraction.ColorExtractor;
@@ -142,6 +141,7 @@
import com.android.wm.shell.sysui.ShellController;
import com.android.wm.shell.sysui.ShellInit;
+import org.junit.After;
import org.junit.Before;
import org.junit.Ignore;
import org.junit.Test;
@@ -152,13 +152,13 @@
import org.mockito.MockitoAnnotations;
import org.mockito.stubbing.Answer;
+import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Optional;
-@FlakyTest
@SmallTest
@RunWith(AndroidTestingRunner.class)
@TestableLooper.RunWithLooper(setAsMainLooper = true)
@@ -411,6 +411,15 @@
.addCallback(mKeyguardStateControllerCallbackCaptor.capture());
}
+ @After
+ public void tearDown() {
+ ArrayList<Bubble> bubbles = new ArrayList<>(mBubbleData.getBubbles());
+ for (int i = 0; i < bubbles.size(); i++) {
+ mBubbleController.removeBubble(bubbles.get(i).getKey(),
+ Bubbles.DISMISS_NO_LONGER_BUBBLE);
+ }
+ }
+
@Test
public void dreamingHidesBubbles() throws RemoteException {
mBubbleController.updateBubble(mBubbleEntry);
diff --git a/packages/SystemUI/unfold/src/com/android/systemui/unfold/progress/PhysicsBasedUnfoldTransitionProgressProvider.kt b/packages/SystemUI/unfold/src/com/android/systemui/unfold/progress/PhysicsBasedUnfoldTransitionProgressProvider.kt
index ecc029d..074b1e1 100644
--- a/packages/SystemUI/unfold/src/com/android/systemui/unfold/progress/PhysicsBasedUnfoldTransitionProgressProvider.kt
+++ b/packages/SystemUI/unfold/src/com/android/systemui/unfold/progress/PhysicsBasedUnfoldTransitionProgressProvider.kt
@@ -16,6 +16,7 @@
package com.android.systemui.unfold.progress
import android.os.Trace
+import android.os.Trace.TRACE_TAG_APP
import android.util.Log
import androidx.dynamicanimation.animation.DynamicAnimation
import androidx.dynamicanimation.animation.FloatPropertyCompat
@@ -157,7 +158,10 @@
}
private fun onStartTransition() {
+ Trace.beginSection( "$TAG#onStartTransition")
listeners.forEach { it.onTransitionStarted() }
+ Trace.endSection()
+
isTransitionRunning = true
if (DEBUG) {
diff --git a/packages/VpnDialogs/res/values-b+sr+Latn/strings.xml b/packages/VpnDialogs/res/values-b+sr+Latn/strings.xml
index 3270744..eaa0aef 100644
--- a/packages/VpnDialogs/res/values-b+sr+Latn/strings.xml
+++ b/packages/VpnDialogs/res/values-b+sr+Latn/strings.xml
@@ -16,24 +16,24 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="prompt" msgid="3183836924226407828">"Zahtev za povezivanje"</string>
- <string name="warning" msgid="809658604548412033">"<xliff:g id="APP">%s</xliff:g> želi da podesi VPN vezu koja omogućava praćenje saobraćaja na mreži. Prihvatite samo ako verujete izvoru. <br /> <br /> <img src=vpn_icon /> se prikazuje u vrhu ekrana kada je VPN aktivan."</string>
- <string name="warning" product="tv" msgid="5188957997628124947">"Aplikacija <xliff:g id="APP">%s</xliff:g> želi da podesi VPN vezu koja joj omogućava da prati mrežni saobraćaj. Prihvatite ovo samo ako imate poverenja u izvor. <br /> <br /> <img src=vpn_icon /> se prikazuje na ekranu kada je VPN aktivan."</string>
- <string name="legacy_title" msgid="192936250066580964">"VPN je povezan"</string>
- <string name="session" msgid="6470628549473641030">"Sesija:"</string>
- <string name="duration" msgid="3584782459928719435">"Trajanje:"</string>
- <string name="data_transmitted" msgid="7988167672982199061">"Poslato:"</string>
- <string name="data_received" msgid="4062776929376067820">"Primljeno:"</string>
- <string name="data_value_format" msgid="2192466557826897580">"<xliff:g id="NUMBER_0">%1$s</xliff:g> bajt(ov)a / <xliff:g id="NUMBER_1">%2$s</xliff:g> paketa"</string>
- <string name="always_on_disconnected_title" msgid="1906740176262776166">"Povezivanje sa uvek uključenim VPN-om nije uspelo"</string>
- <string name="always_on_disconnected_message" msgid="555634519845992917">"Mreža <xliff:g id="VPN_APP_0">%1$s</xliff:g> je podešena da bude uvek povezana, ali trenutno ne može da uspostavi vezu. Telefon će koristiti javnu mrežu dok se ponovo ne poveže sa <xliff:g id="VPN_APP_1">%1$s</xliff:g>."</string>
- <string name="always_on_disconnected_message_lockdown" msgid="4232225539869452120">"Mreža <xliff:g id="VPN_APP">%1$s</xliff:g> je podešena da bude uvek povezana, ali trenutno ne može da uspostavi vezu. Nećete imati vezu dok se VPN ponovo ne poveže."</string>
+ <string name="prompt" msgid="3183836924226407828">"Захтев за повезивање"</string>
+ <string name="warning" msgid="809658604548412033">"<xliff:g id="APP">%s</xliff:g> жели да подеси VPN везу која омогућава праћење саобраћаја на мрежи. Прихватите само ако верујете извору. <br /> <br /> <img src=vpn_icon /> се приказује у врху екрана када је VPN активан."</string>
+ <string name="warning" product="tv" msgid="5188957997628124947">"Апликација <xliff:g id="APP">%s</xliff:g> жели да подеси VPN везу која јој омогућава да прати мрежни саобраћај. Прихватите ово само ако имате поверења у извор. <br /> <br /> <img src=vpn_icon /> се приказује на екрану када је VPN активан."</string>
+ <string name="legacy_title" msgid="192936250066580964">"VPN је повезан"</string>
+ <string name="session" msgid="6470628549473641030">"Сесија:"</string>
+ <string name="duration" msgid="3584782459928719435">"Трајање:"</string>
+ <string name="data_transmitted" msgid="7988167672982199061">"Послато:"</string>
+ <string name="data_received" msgid="4062776929376067820">"Примљенo:"</string>
+ <string name="data_value_format" msgid="2192466557826897580">"<xliff:g id="NUMBER_0">%1$s</xliff:g> бајт(ов)а / <xliff:g id="NUMBER_1">%2$s</xliff:g> пакета"</string>
+ <string name="always_on_disconnected_title" msgid="1906740176262776166">"Повезивање са увек укљученим VPN-ом није успело"</string>
+ <string name="always_on_disconnected_message" msgid="555634519845992917">"Мрежа <xliff:g id="VPN_APP_0">%1$s</xliff:g> је подешена да буде увек повезана, али тренутно не може да успостави везу. Телефон ће користити јавну мрежу док се поново не повеже са <xliff:g id="VPN_APP_1">%1$s</xliff:g>."</string>
+ <string name="always_on_disconnected_message_lockdown" msgid="4232225539869452120">"Мрежа <xliff:g id="VPN_APP">%1$s</xliff:g> је подешена да буде увек повезана, али тренутно не може да успостави везу. Нећете имати везу док се VPN поново не повеже."</string>
<string name="always_on_disconnected_message_separator" msgid="3310614409322581371">" "</string>
- <string name="always_on_disconnected_message_settings_link" msgid="6172280302829992412">"Promeni podešavanja VPN-a"</string>
- <string name="configure" msgid="4905518375574791375">"Konfiguriši"</string>
- <string name="disconnect" msgid="971412338304200056">"Prekini vezu"</string>
- <string name="open_app" msgid="3717639178595958667">"Otvori aplikaciju"</string>
- <string name="dismiss" msgid="6192859333764711227">"Odbaci"</string>
+ <string name="always_on_disconnected_message_settings_link" msgid="6172280302829992412">"Промени подешавања VPN-а"</string>
+ <string name="configure" msgid="4905518375574791375">"Конфигуриши"</string>
+ <string name="disconnect" msgid="971412338304200056">"Прекини везу"</string>
+ <string name="open_app" msgid="3717639178595958667">"Отвори апликацију"</string>
+ <string name="dismiss" msgid="6192859333764711227">"Одбаци"</string>
<string name="sanitized_vpn_label_with_ellipsis" msgid="7014327474633422235">"<xliff:g id="SANITIZED_VPN_LABEL_WITH_ELLIPSIS_0">%1$s</xliff:g>… ( <xliff:g id="SANITIZED_VPN_LABEL_WITH_ELLIPSIS_1">%2$s</xliff:g>)"</string>
<string name="sanitized_vpn_label" msgid="1877415015009794766">"<xliff:g id="SANITIZED_VPN_LABEL_0">%1$s</xliff:g> ( <xliff:g id="SANITIZED_VPN_LABEL_1">%2$s</xliff:g>)"</string>
</resources>
diff --git a/packages/overlays/AvoidAppsInCutoutOverlay/res/values-b+sr+Latn/strings.xml b/packages/overlays/AvoidAppsInCutoutOverlay/res/values-b+sr+Latn/strings.xml
index f80fa8d..c2b611e 100644
--- a/packages/overlays/AvoidAppsInCutoutOverlay/res/values-b+sr+Latn/strings.xml
+++ b/packages/overlays/AvoidAppsInCutoutOverlay/res/values-b+sr+Latn/strings.xml
@@ -17,5 +17,5 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="display_cutout_emulation_overlay" msgid="3814493834951357513">"Prikazuj aplikacije ispod oblasti izreza"</string>
+ <string name="display_cutout_emulation_overlay" msgid="3814493834951357513">"Приказуј апликације испод области изреза"</string>
</resources>
diff --git a/packages/overlays/DisplayCutoutEmulationTallOverlay/res/values-b+sr+Latn/strings.xml b/packages/overlays/DisplayCutoutEmulationTallOverlay/res/values-b+sr+Latn/strings.xml
index 46d30c6..8ee39b8 100644
--- a/packages/overlays/DisplayCutoutEmulationTallOverlay/res/values-b+sr+Latn/strings.xml
+++ b/packages/overlays/DisplayCutoutEmulationTallOverlay/res/values-b+sr+Latn/strings.xml
@@ -17,5 +17,5 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="display_cutout_emulation_overlay" msgid="6424539415439220018">"Izrezana slika za visoke ekrane"</string>
+ <string name="display_cutout_emulation_overlay" msgid="6424539415439220018">"Изрезана слика за високе екране"</string>
</resources>
diff --git a/packages/overlays/DisplayCutoutEmulationWaterfallOverlay/res/values-b+sr+Latn/strings.xml b/packages/overlays/DisplayCutoutEmulationWaterfallOverlay/res/values-b+sr+Latn/strings.xml
index 4cb324c..24e3828 100644
--- a/packages/overlays/DisplayCutoutEmulationWaterfallOverlay/res/values-b+sr+Latn/strings.xml
+++ b/packages/overlays/DisplayCutoutEmulationWaterfallOverlay/res/values-b+sr+Latn/strings.xml
@@ -17,5 +17,5 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="display_cutout_emulation_overlay" msgid="3523556473422419323">"Izrezani slap"</string>
+ <string name="display_cutout_emulation_overlay" msgid="3523556473422419323">"Изрезани слап"</string>
</resources>
diff --git a/packages/overlays/NoCutoutOverlay/res/values-b+sr+Latn/strings.xml b/packages/overlays/NoCutoutOverlay/res/values-b+sr+Latn/strings.xml
index 082e586..26afbf9 100644
--- a/packages/overlays/NoCutoutOverlay/res/values-b+sr+Latn/strings.xml
+++ b/packages/overlays/NoCutoutOverlay/res/values-b+sr+Latn/strings.xml
@@ -17,5 +17,5 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="display_cutout_emulation_overlay" msgid="9031691255599853162">"Sakrij"</string>
+ <string name="display_cutout_emulation_overlay" msgid="9031691255599853162">"Сакриј"</string>
</resources>
diff --git a/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java b/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java
index e92150b..f0b5959 100644
--- a/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java
+++ b/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java
@@ -2504,4 +2504,20 @@
public void attachAccessibilityOverlayToDisplay(int displayId, SurfaceControl sc) {
mSystemSupport.attachAccessibilityOverlayToDisplay(displayId, sc);
}
-}
+
+ @Override
+ public void attachAccessibilityOverlayToWindow(int accessibilityWindowId, SurfaceControl sc)
+ throws RemoteException {
+ synchronized (mLock) {
+ RemoteAccessibilityConnection connection =
+ mA11yWindowManager.getConnectionLocked(
+ mSystemSupport.getCurrentUserIdLocked(),
+ resolveAccessibilityWindowIdLocked(accessibilityWindowId));
+ if (connection == null) {
+ Slog.e(LOG_TAG, "unable to get remote accessibility connection.");
+ return;
+ }
+ connection.getRemote().attachAccessibilityOverlayToWindow(sc);
+ }
+ }
+}
\ No newline at end of file
diff --git a/services/core/Android.bp b/services/core/Android.bp
index 1e1d610..5c00452 100644
--- a/services/core/Android.bp
+++ b/services/core/Android.bp
@@ -161,6 +161,7 @@
"android.hardware.tv.cec-V1-java",
"android.hardware.tv.hdmi-V1-java",
"android.hardware.weaver-V1.0-java",
+ "android.hardware.weaver-V2-java",
"android.hardware.biometrics.face-V1.0-java",
"android.hardware.biometrics.fingerprint-V2.3-java",
"android.hardware.oemlock-V1.0-java",
diff --git a/services/core/java/com/android/server/EventLogTags.logtags b/services/core/java/com/android/server/EventLogTags.logtags
index 68fd0c1..274370e 100644
--- a/services/core/java/com/android/server/EventLogTags.logtags
+++ b/services/core/java/com/android/server/EventLogTags.logtags
@@ -185,6 +185,10 @@
# ---------------------------
# Re-connecting to input method service because we haven't received its interface
32000 imf_force_reconnect_ime (IME|4),(Time Since Connect|2|3),(Showing|1|1)
+# Indicates server show input method
+32001 imf_show_ime (token|3),(window|3),(reason|3),(softInputMode|3)
+# Indicates server hide input method
+32002 imf_hide_ime (token|3),(window|3),(reason|3),(softInputMode|3)
# ---------------------------
diff --git a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
index 4a6e5a3..7f2e5fb 100644
--- a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
+++ b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
@@ -1350,6 +1350,9 @@
final InputStream mInput;
final String mGdbPort;
final boolean mMonkey;
+ final boolean mSimpleMode;
+ final String mTarget;
+ final boolean mAlwaysContinue;
static final int STATE_NORMAL = 0;
static final int STATE_CRASHED = 1;
@@ -1377,16 +1380,30 @@
boolean mGotGdbPrint;
MyActivityController(IActivityManager iam, PrintWriter pw, InputStream input,
- String gdbPort, boolean monkey) {
+ String gdbPort, boolean monkey, boolean simpleMode, String target,
+ boolean alwaysContinue) {
mInterface = iam;
mPw = pw;
mInput = input;
mGdbPort = gdbPort;
mMonkey = monkey;
+ mSimpleMode = simpleMode;
+ mTarget = target;
+ mAlwaysContinue = alwaysContinue;
+ }
+
+ private boolean shouldHandlePackageOrProcess(String packageOrProcess) {
+ if (mTarget == null) {
+ return true; // Always handle all packages / processes.
+ }
+ return mTarget.equals(packageOrProcess);
}
@Override
public boolean activityResuming(String pkg) {
+ if (!shouldHandlePackageOrProcess(pkg)) {
+ return true;
+ }
synchronized (this) {
mPw.println("** Activity resuming: " + pkg);
mPw.flush();
@@ -1396,6 +1413,9 @@
@Override
public boolean activityStarting(Intent intent, String pkg) {
+ if (!shouldHandlePackageOrProcess(pkg)) {
+ return true;
+ }
synchronized (this) {
mPw.println("** Activity starting: " + pkg);
mPw.flush();
@@ -1406,18 +1426,28 @@
@Override
public boolean appCrashed(String processName, int pid, String shortMsg, String longMsg,
long timeMillis, String stackTrace) {
+ if (!shouldHandlePackageOrProcess(processName)) {
+ return true; // Don't kill
+ }
synchronized (this) {
- mPw.println("** ERROR: PROCESS CRASHED");
- mPw.println("processName: " + processName);
- mPw.println("processPid: " + pid);
- mPw.println("shortMsg: " + shortMsg);
- mPw.println("longMsg: " + longMsg);
- mPw.println("timeMillis: " + timeMillis);
- mPw.println("uptime: " + SystemClock.uptimeMillis());
- mPw.println("stack:");
- mPw.print(stackTrace);
- mPw.println("#");
+ if (mSimpleMode) {
+ mPw.println("** PROCESS CRASHED: " + processName);
+ } else {
+ mPw.println("** ERROR: PROCESS CRASHED");
+ mPw.println("processName: " + processName);
+ mPw.println("processPid: " + pid);
+ mPw.println("shortMsg: " + shortMsg);
+ mPw.println("longMsg: " + longMsg);
+ mPw.println("timeMillis: " + timeMillis);
+ mPw.println("uptime: " + SystemClock.uptimeMillis());
+ mPw.println("stack:");
+ mPw.print(stackTrace);
+ mPw.println("#");
+ }
mPw.flush();
+ if (mAlwaysContinue) {
+ return true;
+ }
int result = waitControllerLocked(pid, STATE_CRASHED);
return result == RESULT_CRASH_KILL ? false : true;
}
@@ -1425,13 +1455,23 @@
@Override
public int appEarlyNotResponding(String processName, int pid, String annotation) {
+ if (!shouldHandlePackageOrProcess(processName)) {
+ return 0; // Continue
+ }
synchronized (this) {
- mPw.println("** ERROR: EARLY PROCESS NOT RESPONDING");
- mPw.println("processName: " + processName);
- mPw.println("processPid: " + pid);
- mPw.println("annotation: " + annotation);
- mPw.println("uptime: " + SystemClock.uptimeMillis());
+ if (mSimpleMode) {
+ mPw.println("** EARLY PROCESS NOT RESPONDING: " + processName);
+ } else {
+ mPw.println("** ERROR: EARLY PROCESS NOT RESPONDING");
+ mPw.println("processName: " + processName);
+ mPw.println("processPid: " + pid);
+ mPw.println("annotation: " + annotation);
+ mPw.println("uptime: " + SystemClock.uptimeMillis());
+ }
mPw.flush();
+ if (mAlwaysContinue) {
+ return 0;
+ }
int result = waitControllerLocked(pid, STATE_EARLY_ANR);
if (result == RESULT_EARLY_ANR_KILL) return -1;
return 0;
@@ -1440,15 +1480,25 @@
@Override
public int appNotResponding(String processName, int pid, String processStats) {
+ if (!shouldHandlePackageOrProcess(processName)) {
+ return 0; // Default == show dialog
+ }
synchronized (this) {
- mPw.println("** ERROR: PROCESS NOT RESPONDING");
- mPw.println("processName: " + processName);
- mPw.println("processPid: " + pid);
- mPw.println("uptime: " + SystemClock.uptimeMillis());
- mPw.println("processStats:");
- mPw.print(processStats);
- mPw.println("#");
+ if (mSimpleMode) {
+ mPw.println("** PROCESS NOT RESPONDING: " + processName);
+ } else {
+ mPw.println("** ERROR: PROCESS NOT RESPONDING");
+ mPw.println("processName: " + processName);
+ mPw.println("processPid: " + pid);
+ mPw.println("uptime: " + SystemClock.uptimeMillis());
+ mPw.println("processStats:");
+ mPw.print(processStats);
+ mPw.println("#");
+ }
mPw.flush();
+ if (mAlwaysContinue) {
+ return 0;
+ }
int result = waitControllerLocked(pid, STATE_ANR);
if (result == RESULT_ANR_KILL) return -1;
if (result == RESULT_ANR_WAIT) return 1;
@@ -1458,11 +1508,16 @@
@Override
public int systemNotResponding(String message) {
+ if (mTarget != null) {
+ return -1; // If any target is set, just return.
+ }
synchronized (this) {
mPw.println("** ERROR: PROCESS NOT RESPONDING");
- mPw.println("message: " + message);
- mPw.println("#");
- mPw.println("Allowing system to die.");
+ if (!mSimpleMode) {
+ mPw.println("message: " + message);
+ mPw.println("#");
+ mPw.println("Allowing system to die.");
+ }
mPw.flush();
return -1;
}
@@ -1568,6 +1623,9 @@
}
void printMessageForState() {
+ if (mAlwaysContinue && mSimpleMode) {
+ return; // In the simplest mode, we don't need to show anything.
+ }
switch (mState) {
case STATE_NORMAL:
mPw.println("Monitoring activity manager... available commands:");
@@ -1663,11 +1721,21 @@
String opt;
String gdbPort = null;
boolean monkey = false;
+ boolean simpleMode = false;
+ boolean alwaysContinue = false;
+ String target = null;
+
while ((opt=getNextOption()) != null) {
if (opt.equals("--gdb")) {
gdbPort = getNextArgRequired();
+ } else if (opt.equals("-p")) {
+ target = getNextArgRequired();
} else if (opt.equals("-m")) {
monkey = true;
+ } else if (opt.equals("-s")) {
+ simpleMode = true;
+ } else if (opt.equals("-c")) {
+ alwaysContinue = true;
} else {
getErrPrintWriter().println("Error: Unknown option: " + opt);
return -1;
@@ -1675,7 +1743,7 @@
}
MyActivityController controller = new MyActivityController(mInterface, pw,
- getRawInputStream(), gdbPort, monkey);
+ getRawInputStream(), gdbPort, monkey, simpleMode, target, alwaysContinue);
controller.run();
return 0;
}
@@ -3896,9 +3964,12 @@
pw.println(" make-uid-idle [--user <USER_ID> | all | current] <PACKAGE>");
pw.println(" If the given application's uid is in the background and waiting to");
pw.println(" become idle (not allowing background services), do that now.");
- pw.println(" monitor [--gdb <port>]");
+ pw.println(" monitor [--gdb <port>] [-p <TARGET>] [-s] [-c]");
pw.println(" Start monitoring for crashes or ANRs.");
pw.println(" --gdb: start gdbserv on the given port at crash/ANR");
+ pw.println(" -p: only show events related to a specific process / package");
+ pw.println(" -s: simple mode, only show a summary line for each event");
+ pw.println(" -c: assume the input is always [c]ontinue");
pw.println(" watch-uids [--oom <uid>]");
pw.println(" Start watching for and reporting uid state changes.");
pw.println(" --oom: specify a uid for which to report detailed change messages.");
diff --git a/services/core/java/com/android/server/appop/AppOpsCheckingServiceImpl.java b/services/core/java/com/android/server/appop/AppOpsCheckingServiceImpl.java
index ac25f4e..ef0de18 100644
--- a/services/core/java/com/android/server/appop/AppOpsCheckingServiceImpl.java
+++ b/services/core/java/com/android/server/appop/AppOpsCheckingServiceImpl.java
@@ -218,13 +218,13 @@
}
@Override
- public boolean arePackageModesDefault(String packageMode, @UserIdInt int userId) {
+ public boolean arePackageModesDefault(@NonNull String packageName, @UserIdInt int userId) {
synchronized (mLock) {
ArrayMap<String, SparseIntArray> packageModes = mUserPackageModes.get(userId, null);
if (packageModes == null) {
return true;
}
- SparseIntArray opModes = packageModes.get(packageMode);
+ SparseIntArray opModes = packageModes.get(packageName);
return (opModes == null || opModes.size() <= 0);
}
}
diff --git a/services/core/java/com/android/server/audio/SoundDoseHelper.java b/services/core/java/com/android/server/audio/SoundDoseHelper.java
index 5fe9ada..6279a4d 100644
--- a/services/core/java/com/android/server/audio/SoundDoseHelper.java
+++ b/services/core/java/com/android/server/audio/SoundDoseHelper.java
@@ -66,7 +66,7 @@
private static final String TAG = "AS.SoundDoseHelper";
/*package*/ static final String ACTION_CHECK_MUSIC_ACTIVE =
- AudioService.class.getSimpleName() + ".CHECK_MUSIC_ACTIVE";
+ "com.android.server.audio.action.CHECK_MUSIC_ACTIVE";
/** Flag to enable/disable the sound dose computation. */
private static final boolean USE_CSD_FOR_SAFE_HEARING = false;
diff --git a/services/core/java/com/android/server/display/AutomaticBrightnessController.java b/services/core/java/com/android/server/display/AutomaticBrightnessController.java
index 197c64e..4f00835 100644
--- a/services/core/java/com/android/server/display/AutomaticBrightnessController.java
+++ b/services/core/java/com/android/server/display/AutomaticBrightnessController.java
@@ -249,7 +249,7 @@
HysteresisLevels screenBrightnessThresholdsIdle, Context context,
HighBrightnessModeController hbmController, BrightnessThrottler brightnessThrottler,
BrightnessMappingStrategy idleModeBrightnessMapper, int ambientLightHorizonShort,
- int ambientLightHorizonLong) {
+ int ambientLightHorizonLong, float userLux, float userBrightness) {
this(new Injector(), callbacks, looper, sensorManager, lightSensor,
interactiveModeBrightnessMapper,
lightSensorWarmUpTime, brightnessMin, brightnessMax, dozeScaleFactor,
@@ -258,7 +258,7 @@
ambientBrightnessThresholds, screenBrightnessThresholds,
ambientBrightnessThresholdsIdle, screenBrightnessThresholdsIdle, context,
hbmController, brightnessThrottler, idleModeBrightnessMapper,
- ambientLightHorizonShort, ambientLightHorizonLong
+ ambientLightHorizonShort, ambientLightHorizonLong, userLux, userBrightness
);
}
@@ -275,7 +275,7 @@
HysteresisLevels screenBrightnessThresholdsIdle, Context context,
HighBrightnessModeController hbmController, BrightnessThrottler brightnessThrottler,
BrightnessMappingStrategy idleModeBrightnessMapper, int ambientLightHorizonShort,
- int ambientLightHorizonLong) {
+ int ambientLightHorizonLong, float userLux, float userBrightness) {
mInjector = injector;
mClock = injector.createClock();
mContext = context;
@@ -322,6 +322,12 @@
mIdleModeBrightnessMapper = idleModeBrightnessMapper;
// Initialize to active (normal) screen brightness mode
switchToInteractiveScreenBrightnessMode();
+
+ if (userLux != BrightnessMappingStrategy.NO_USER_LUX
+ && userBrightness != BrightnessMappingStrategy.NO_USER_BRIGHTNESS) {
+ // Use the given short-term model
+ setScreenBrightnessByUser(userLux, userBrightness);
+ }
}
/**
@@ -384,7 +390,8 @@
public void configure(int state, @Nullable BrightnessConfiguration configuration,
float brightness, boolean userChangedBrightness, float adjustment,
- boolean userChangedAutoBrightnessAdjustment, int displayPolicy) {
+ boolean userChangedAutoBrightnessAdjustment, int displayPolicy,
+ boolean shouldResetShortTermModel) {
mState = state;
mHbmController.setAutoBrightnessEnabled(mState);
// While dozing, the application processor may be suspended which will prevent us from
@@ -393,7 +400,7 @@
// and hold onto the last computed screen auto brightness. We save the dozing flag for
// debugging purposes.
boolean dozing = (displayPolicy == DisplayPowerRequest.POLICY_DOZE);
- boolean changed = setBrightnessConfiguration(configuration);
+ boolean changed = setBrightnessConfiguration(configuration, shouldResetShortTermModel);
changed |= setDisplayPolicy(displayPolicy);
if (userChangedAutoBrightnessAdjustment) {
changed |= setAutoBrightnessAdjustment(adjustment);
@@ -492,9 +499,13 @@
// and we can't use this data to add a new control point to the short-term model.
return false;
}
- mCurrentBrightnessMapper.addUserDataPoint(mAmbientLux, brightness);
+ return setScreenBrightnessByUser(mAmbientLux, brightness);
+ }
+
+ private boolean setScreenBrightnessByUser(float lux, float brightness) {
+ mCurrentBrightnessMapper.addUserDataPoint(lux, brightness);
mShortTermModelValid = true;
- mShortTermModelAnchor = mAmbientLux;
+ mShortTermModelAnchor = lux;
if (mLoggingEnabled) {
Slog.d(TAG, "ShortTermModel: anchor=" + mShortTermModelAnchor);
}
@@ -514,9 +525,10 @@
mShortTermModelValid = false;
}
- public boolean setBrightnessConfiguration(BrightnessConfiguration configuration) {
+ public boolean setBrightnessConfiguration(BrightnessConfiguration configuration,
+ boolean shouldResetShortTermModel) {
if (mInteractiveModeBrightnessMapper.setBrightnessConfiguration(configuration)) {
- if (!isInIdleMode()) {
+ if (!isInIdleMode() && shouldResetShortTermModel) {
resetShortTermModel();
}
return true;
diff --git a/services/core/java/com/android/server/display/BrightnessMappingStrategy.java b/services/core/java/com/android/server/display/BrightnessMappingStrategy.java
index 25d0752..3fc50c4 100644
--- a/services/core/java/com/android/server/display/BrightnessMappingStrategy.java
+++ b/services/core/java/com/android/server/display/BrightnessMappingStrategy.java
@@ -51,6 +51,9 @@
public abstract class BrightnessMappingStrategy {
private static final String TAG = "BrightnessMappingStrategy";
+ public static final float NO_USER_LUX = -1;
+ public static final float NO_USER_BRIGHTNESS = -1;
+
private static final float LUX_GRAD_SMOOTHING = 0.25f;
private static final float MAX_GRAD = 1.0f;
private static final float SHORT_TERM_MODEL_THRESHOLD_RATIO = 0.6f;
@@ -68,6 +71,7 @@
* Creates a BrightnessMappingStrategy for active (normal) mode.
* @param resources
* @param displayDeviceConfig
+ * @param displayWhiteBalanceController
* @return the BrightnessMappingStrategy
*/
@Nullable
@@ -82,6 +86,7 @@
* Creates a BrightnessMappingStrategy for idle screen brightness mode.
* @param resources
* @param displayDeviceConfig
+ * @param displayWhiteBalanceController
* @return the BrightnessMappingStrategy
*/
@Nullable
@@ -100,6 +105,7 @@
* @param displayDeviceConfig
* @param isForIdleMode determines whether the configurations loaded are for idle screen
* brightness mode or active screen brightness mode.
+ * @param displayWhiteBalanceController
* @return the BrightnessMappingStrategy
*/
@Nullable
@@ -370,6 +376,10 @@
*/
public abstract boolean isForIdleMode();
+ abstract float getUserLux();
+
+ abstract float getUserBrightness();
+
/**
* Check if the short term model should be reset given the anchor lux the last
* brightness change was made at and the current ambient lux.
@@ -604,8 +614,8 @@
mMaxGamma = maxGamma;
mAutoBrightnessAdjustment = 0;
- mUserLux = -1;
- mUserBrightness = -1;
+ mUserLux = NO_USER_LUX;
+ mUserBrightness = NO_USER_BRIGHTNESS;
if (mLoggingEnabled) {
PLOG.start("simple mapping strategy");
}
@@ -732,6 +742,16 @@
return false;
}
+ @Override
+ float getUserLux() {
+ return mUserLux;
+ }
+
+ @Override
+ float getUserBrightness() {
+ return mUserBrightness;
+ }
+
private void computeSpline() {
Pair<float[], float[]> curve = getAdjustedCurve(mLux, mBrightness, mUserLux,
mUserBrightness, mAutoBrightnessAdjustment, mMaxGamma);
@@ -799,8 +819,8 @@
mIsForIdleMode = isForIdleMode;
mMaxGamma = maxGamma;
mAutoBrightnessAdjustment = 0;
- mUserLux = -1;
- mUserBrightness = -1;
+ mUserLux = NO_USER_LUX;
+ mUserBrightness = NO_USER_BRIGHTNESS;
mDisplayWhiteBalanceController = displayWhiteBalanceController;
mNits = nits;
@@ -972,6 +992,16 @@
return mIsForIdleMode;
}
+ @Override
+ float getUserLux() {
+ return mUserLux;
+ }
+
+ @Override
+ float getUserBrightness() {
+ return mUserBrightness;
+ }
+
/**
* Prints out the default curve and how it differs from the long-term curve
* and the current curve (in case the current curve includes short-term adjustments).
diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java
index 329e3ca..5e9f0e7 100644
--- a/services/core/java/com/android/server/display/DisplayManagerService.java
+++ b/services/core/java/com/android/server/display/DisplayManagerService.java
@@ -594,7 +594,7 @@
getBrightnessConfigForDisplayWithPdsFallbackLocked(
logicalDisplay.getPrimaryDisplayDeviceLocked().getUniqueId(),
userSerial);
- dpc.setBrightnessConfiguration(config);
+ dpc.setBrightnessConfiguration(config, /* shouldResetShortTermModel= */ true);
}
dpc.onSwitchUser(newUserId);
});
@@ -1934,7 +1934,7 @@
}
DisplayPowerControllerInterface dpc = getDpcFromUniqueIdLocked(uniqueId);
if (dpc != null) {
- dpc.setBrightnessConfiguration(c);
+ dpc.setBrightnessConfiguration(c, /* shouldResetShortTermModel= */ true);
}
}
}
@@ -1983,7 +1983,8 @@
final DisplayPowerControllerInterface dpc = mDisplayPowerControllers.get(
logicalDisplay.getDisplayIdLocked());
if (dpc != null) {
- dpc.setBrightnessConfiguration(config);
+ dpc.setBrightnessConfiguration(config,
+ /* shouldResetShortTermModel= */ false);
}
}
});
diff --git a/services/core/java/com/android/server/display/DisplayPowerController.java b/services/core/java/com/android/server/display/DisplayPowerController.java
index e4fcdbd..f4eed2b 100644
--- a/services/core/java/com/android/server/display/DisplayPowerController.java
+++ b/services/core/java/com/android/server/display/DisplayPowerController.java
@@ -225,6 +225,9 @@
// True if should use light sensor to automatically determine doze screen brightness.
private final boolean mAllowAutoBrightnessWhileDozingConfig;
+ // True if the brightness config has changed and the short-term model needs to be reset
+ private boolean mShouldResetShortTermModel;
+
// Whether or not the color fade on screen on / off is enabled.
private final boolean mColorFadeEnabled;
@@ -951,6 +954,13 @@
return;
}
+ float userLux = BrightnessMappingStrategy.NO_USER_LUX;
+ float userBrightness = BrightnessMappingStrategy.NO_USER_BRIGHTNESS;
+ if (mInteractiveModeBrightnessMapper != null) {
+ userLux = mInteractiveModeBrightnessMapper.getUserLux();
+ userBrightness = mInteractiveModeBrightnessMapper.getUserBrightness();
+ }
+
final boolean isIdleScreenBrightnessEnabled = resources.getBoolean(
R.bool.config_enableIdleScreenBrightnessMode);
mInteractiveModeBrightnessMapper = BrightnessMappingStrategy.create(resources,
@@ -1078,7 +1088,7 @@
ambientBrightnessThresholdsIdle, screenBrightnessThresholdsIdle, mContext,
mHbmController, mBrightnessThrottler, mIdleModeBrightnessMapper,
mDisplayDeviceConfig.getAmbientHorizonShort(),
- mDisplayDeviceConfig.getAmbientHorizonLong());
+ mDisplayDeviceConfig.getAmbientHorizonLong(), userLux, userBrightness);
mBrightnessEventRingBuffer =
new RingBuffer<>(BrightnessEvent.class, RINGBUFFER_MAX);
@@ -1428,7 +1438,9 @@
mBrightnessConfiguration,
mLastUserSetScreenBrightness,
userSetBrightnessChanged, autoBrightnessAdjustment,
- autoBrightnessAdjustmentChanged, mPowerRequest.policy);
+ autoBrightnessAdjustmentChanged, mPowerRequest.policy,
+ mShouldResetShortTermModel);
+ mShouldResetShortTermModel = false;
}
if (mBrightnessTracker != null) {
@@ -1840,8 +1852,10 @@
}
@Override
- public void setBrightnessConfiguration(BrightnessConfiguration c) {
- Message msg = mHandler.obtainMessage(MSG_CONFIGURE_BRIGHTNESS, c);
+ public void setBrightnessConfiguration(BrightnessConfiguration c,
+ boolean shouldResetShortTermModel) {
+ Message msg = mHandler.obtainMessage(MSG_CONFIGURE_BRIGHTNESS,
+ shouldResetShortTermModel ? 1 : 0, /* unused */ 0, c);
msg.sendToTarget();
}
@@ -2892,6 +2906,7 @@
break;
case MSG_CONFIGURE_BRIGHTNESS:
mBrightnessConfiguration = (BrightnessConfiguration) msg.obj;
+ mShouldResetShortTermModel = msg.arg1 == 1;
updatePowerState();
break;
diff --git a/services/core/java/com/android/server/display/DisplayPowerController2.java b/services/core/java/com/android/server/display/DisplayPowerController2.java
index 81011dc..09136b0 100644
--- a/services/core/java/com/android/server/display/DisplayPowerController2.java
+++ b/services/core/java/com/android/server/display/DisplayPowerController2.java
@@ -202,6 +202,9 @@
// True if auto-brightness should be used.
private boolean mUseSoftwareAutoBrightnessConfig;
+ // True if the brightness config has changed and the short-term model needs to be reset
+ private boolean mShouldResetShortTermModel;
+
// Whether or not the color fade on screen on / off is enabled.
private final boolean mColorFadeEnabled;
@@ -868,6 +871,13 @@
return;
}
+ float userLux = BrightnessMappingStrategy.NO_USER_LUX;
+ float userBrightness = BrightnessMappingStrategy.NO_USER_BRIGHTNESS;
+ if (mInteractiveModeBrightnessMapper != null) {
+ userLux = mInteractiveModeBrightnessMapper.getUserLux();
+ userBrightness = mInteractiveModeBrightnessMapper.getUserBrightness();
+ }
+
final boolean isIdleScreenBrightnessEnabled = resources.getBoolean(
R.bool.config_enableIdleScreenBrightnessMode);
mInteractiveModeBrightnessMapper = BrightnessMappingStrategy.create(resources,
@@ -995,7 +1005,7 @@
ambientBrightnessThresholdsIdle, screenBrightnessThresholdsIdle, mContext,
mHbmController, mBrightnessThrottler, mIdleModeBrightnessMapper,
mDisplayDeviceConfig.getAmbientHorizonShort(),
- mDisplayDeviceConfig.getAmbientHorizonLong());
+ mDisplayDeviceConfig.getAmbientHorizonLong(), userLux, userBrightness);
mBrightnessEventRingBuffer =
new RingBuffer<>(BrightnessEvent.class, RINGBUFFER_MAX);
@@ -1220,7 +1230,9 @@
mBrightnessConfiguration,
mLastUserSetScreenBrightness,
userSetBrightnessChanged, autoBrightnessAdjustment,
- autoBrightnessAdjustmentChanged, mPowerRequest.policy);
+ autoBrightnessAdjustmentChanged, mPowerRequest.policy,
+ mShouldResetShortTermModel);
+ mShouldResetShortTermModel = false;
}
if (mBrightnessTracker != null) {
@@ -1626,8 +1638,10 @@
}
@Override
- public void setBrightnessConfiguration(BrightnessConfiguration c) {
- Message msg = mHandler.obtainMessage(MSG_CONFIGURE_BRIGHTNESS, c);
+ public void setBrightnessConfiguration(BrightnessConfiguration c,
+ boolean shouldResetShortTermModel) {
+ Message msg = mHandler.obtainMessage(MSG_CONFIGURE_BRIGHTNESS,
+ shouldResetShortTermModel ? 1 : 0, /* unused */ 0, c);
msg.sendToTarget();
}
@@ -2485,6 +2499,7 @@
break;
case MSG_CONFIGURE_BRIGHTNESS:
mBrightnessConfiguration = (BrightnessConfiguration) msg.obj;
+ mShouldResetShortTermModel = msg.arg1 == 1;
updatePowerState();
break;
diff --git a/services/core/java/com/android/server/display/DisplayPowerControllerInterface.java b/services/core/java/com/android/server/display/DisplayPowerControllerInterface.java
index 46f1343..e750ee2 100644
--- a/services/core/java/com/android/server/display/DisplayPowerControllerInterface.java
+++ b/services/core/java/com/android/server/display/DisplayPowerControllerInterface.java
@@ -48,7 +48,8 @@
* Used to update the display's BrightnessConfiguration
* @param config The new BrightnessConfiguration
*/
- void setBrightnessConfiguration(BrightnessConfiguration config);
+ void setBrightnessConfiguration(BrightnessConfiguration config,
+ boolean shouldResetShortTermModel);
/**
* Used to set the ambient color temperature of the Display
diff --git a/services/core/java/com/android/server/graphics/fonts/FontManagerShellCommand.java b/services/core/java/com/android/server/graphics/fonts/FontManagerShellCommand.java
index 4cd0d6e..2ac2833 100644
--- a/services/core/java/com/android/server/graphics/fonts/FontManagerShellCommand.java
+++ b/services/core/java/com/android/server/graphics/fonts/FontManagerShellCommand.java
@@ -163,19 +163,25 @@
// Dump named font family first.
List<FontConfig.FontFamily> families = fontConfig.getFontFamilies();
- w.println("Named Font Families");
+ // Dump FontFamilyList
+ w.println("Named Family List");
w.increaseIndent();
- for (int i = 0; i < families.size(); ++i) {
- final FontConfig.FontFamily family = families.get(i);
-
- // Here, only dump the named family only.
- if (family.getName() == null) continue;
-
- w.println("Named Family (" + family.getName() + ")");
- final List<FontConfig.Font> fonts = family.getFontList();
+ List<FontConfig.NamedFamilyList> namedFamilyLists = fontConfig.getNamedFamilyLists();
+ for (int i = 0; i < namedFamilyLists.size(); ++i) {
+ final FontConfig.NamedFamilyList namedFamilyList = namedFamilyLists.get(i);
+ w.println("Named Family (" + namedFamilyList.getName() + ")");
w.increaseIndent();
- for (int j = 0; j < fonts.size(); ++j) {
- dumpSingleFontConfig(w, fonts.get(j));
+ final List<FontConfig.FontFamily> namedFamilies = namedFamilyList.getFamilies();
+ for (int j = 0; j < namedFamilies.size(); ++j) {
+ final FontConfig.FontFamily family = namedFamilies.get(j);
+
+ w.println("Family");
+ final List<FontConfig.Font> fonts = family.getFontList();
+ w.increaseIndent();
+ for (int k = 0; k < fonts.size(); ++k) {
+ dumpSingleFontConfig(w, fonts.get(k));
+ }
+ w.decreaseIndent();
}
w.decreaseIndent();
}
diff --git a/services/core/java/com/android/server/graphics/fonts/UpdatableFontDir.java b/services/core/java/com/android/server/graphics/fonts/UpdatableFontDir.java
index 6f93608..a680f50 100644
--- a/services/core/java/com/android/server/graphics/fonts/UpdatableFontDir.java
+++ b/services/core/java/com/android/server/graphics/fonts/UpdatableFontDir.java
@@ -19,7 +19,6 @@
import static com.android.server.graphics.fonts.FontManagerService.SystemFontException;
import android.annotation.NonNull;
-import android.annotation.Nullable;
import android.graphics.fonts.FontManager;
import android.graphics.fonts.FontUpdateRequest;
import android.graphics.fonts.SystemFonts;
@@ -44,6 +43,7 @@
import java.nio.file.Paths;
import java.security.SecureRandom;
import java.util.ArrayList;
+import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@@ -286,7 +286,7 @@
// Before processing font family update, check all family points the available fonts.
for (FontUpdateRequest.Family family : familyMap.values()) {
- if (resolveFontFiles(family) == null) {
+ if (resolveFontFilesForNamedFamily(family) == null) {
throw new SystemFontException(
FontManager.RESULT_ERROR_FONT_NOT_FOUND,
"Required fonts are not available");
@@ -498,6 +498,19 @@
}
}
}
+ for (int i = 0; i < fontConfig.getNamedFamilyLists().size(); ++i) {
+ FontConfig.NamedFamilyList namedFamilyList = fontConfig.getNamedFamilyLists().get(i);
+ for (int j = 0; j < namedFamilyList.getFamilies().size(); ++j) {
+ FontConfig.FontFamily family = namedFamilyList.getFamilies().get(j);
+ for (int k = 0; k < family.getFontList().size(); ++k) {
+ FontConfig.Font font = family.getFontList().get(k);
+ if (font.getPostScriptName().equals(psName)) {
+ targetFont = font;
+ break;
+ }
+ }
+ }
+ }
if (targetFont == null) {
return -1;
}
@@ -553,8 +566,8 @@
}
}
- @Nullable
- private FontConfig.FontFamily resolveFontFiles(FontUpdateRequest.Family fontFamily) {
+ private FontConfig.NamedFamilyList resolveFontFilesForNamedFamily(
+ FontUpdateRequest.Family fontFamily) {
List<FontUpdateRequest.Font> fontList = fontFamily.getFonts();
List<FontConfig.Font> resolvedFonts = new ArrayList<>(fontList.size());
for (int i = 0; i < fontList.size(); i++) {
@@ -567,8 +580,10 @@
resolvedFonts.add(new FontConfig.Font(info.mFile, null, info.getPostScriptName(),
font.getFontStyle(), font.getIndex(), font.getFontVariationSettings(), null));
}
- return new FontConfig.FontFamily(resolvedFonts, fontFamily.getName(),
+ FontConfig.FontFamily family = new FontConfig.FontFamily(resolvedFonts,
LocaleList.getEmptyLocaleList(), FontConfig.FontFamily.VARIANT_DEFAULT);
+ return new FontConfig.NamedFamilyList(Collections.singletonList(family),
+ fontFamily.getName());
}
Map<String, File> getPostScriptMap() {
@@ -585,23 +600,24 @@
PersistentSystemFontConfig.Config persistentConfig = readPersistentConfig();
List<FontUpdateRequest.Family> families = persistentConfig.fontFamilies;
- List<FontConfig.FontFamily> mergedFamilies =
- new ArrayList<>(config.getFontFamilies().size() + families.size());
+ List<FontConfig.NamedFamilyList> mergedFamilies =
+ new ArrayList<>(config.getNamedFamilyLists().size() + families.size());
// We should keep the first font family (config.getFontFamilies().get(0)) because it's used
// as a fallback font. See SystemFonts.java.
- mergedFamilies.addAll(config.getFontFamilies());
+ mergedFamilies.addAll(config.getNamedFamilyLists());
// When building Typeface, a latter font family definition will override the previous font
// family definition with the same name. An exception is config.getFontFamilies.get(0),
// which will be used as a fallback font without being overridden.
for (int i = 0; i < families.size(); ++i) {
- FontConfig.FontFamily family = resolveFontFiles(families.get(i));
+ FontConfig.NamedFamilyList family = resolveFontFilesForNamedFamily(families.get(i));
if (family != null) {
mergedFamilies.add(family);
}
}
return new FontConfig(
- mergedFamilies, config.getAliases(), mLastModifiedMillis, mConfigVersion);
+ config.getFontFamilies(), config.getAliases(), mergedFamilies, mLastModifiedMillis,
+ mConfigVersion);
}
private PersistentSystemFontConfig.Config readPersistentConfig() {
@@ -635,12 +651,12 @@
return mConfigVersion;
}
- public Map<String, FontConfig.FontFamily> getFontFamilyMap() {
+ public Map<String, FontConfig.NamedFamilyList> getFontFamilyMap() {
PersistentSystemFontConfig.Config curConfig = readPersistentConfig();
- Map<String, FontConfig.FontFamily> familyMap = new HashMap<>();
+ Map<String, FontConfig.NamedFamilyList> familyMap = new HashMap<>();
for (int i = 0; i < curConfig.fontFamilies.size(); ++i) {
FontUpdateRequest.Family family = curConfig.fontFamilies.get(i);
- FontConfig.FontFamily resolvedFamily = resolveFontFiles(family);
+ FontConfig.NamedFamilyList resolvedFamily = resolveFontFilesForNamedFamily(family);
if (resolvedFamily != null) {
familyMap.put(family.getName(), resolvedFamily);
}
diff --git a/services/core/java/com/android/server/inputmethod/AdditionalSubtypeUtils.java b/services/core/java/com/android/server/inputmethod/AdditionalSubtypeUtils.java
index 4b040fa..c05a03e 100644
--- a/services/core/java/com/android/server/inputmethod/AdditionalSubtypeUtils.java
+++ b/services/core/java/com/android/server/inputmethod/AdditionalSubtypeUtils.java
@@ -18,6 +18,7 @@
import android.annotation.NonNull;
import android.annotation.UserIdInt;
+import android.icu.util.ULocale;
import android.os.Environment;
import android.os.FileUtils;
import android.os.UserHandle;
@@ -29,6 +30,7 @@
import android.view.inputmethod.InputMethodInfo;
import android.view.inputmethod.InputMethodSubtype;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.modules.utils.TypedXmlPullParser;
import com.android.modules.utils.TypedXmlSerializer;
@@ -59,6 +61,8 @@
private static final String ATTR_ID = "id";
private static final String ATTR_LABEL = "label";
private static final String ATTR_NAME_OVERRIDE = "nameOverride";
+ private static final String ATTR_NAME_PK_LANGUAGE_TAG = "pkLanguageTag";
+ private static final String ATTR_NAME_PK_LAYOUT_TYPE = "pkLayoutType";
private static final String ATTR_ICON = "icon";
private static final String ATTR_IME_SUBTYPE_ID = "subtypeId";
private static final String ATTR_IME_SUBTYPE_LOCALE = "imeSubtypeLocale";
@@ -74,7 +78,7 @@
/**
* Returns a {@link File} that represents the directory at which subtype.xml will be placed.
*
- * @param userId User ID with with subtype.xml path should be determined.
+ * @param userId User ID with subtype.xml path should be determined.
* @return {@link File} that represents the directory.
*/
@NonNull
@@ -134,11 +138,15 @@
Slog.e(TAG, "Failed to create a parent directory " + inputMethodDir);
return;
}
+ saveToFile(allSubtypes, methodMap, getAdditionalSubtypeFile(inputMethodDir));
+ }
+ @VisibleForTesting
+ static void saveToFile(ArrayMap<String, List<InputMethodSubtype>> allSubtypes,
+ ArrayMap<String, InputMethodInfo> methodMap, AtomicFile subtypesFile) {
// Safety net for the case that this function is called before methodMap is set.
final boolean isSetMethodMap = methodMap != null && methodMap.size() > 0;
FileOutputStream fos = null;
- final AtomicFile subtypesFile = getAdditionalSubtypeFile(inputMethodDir);
try {
fos = subtypesFile.startWrite();
final TypedXmlSerializer out = Xml.resolveSerializer(fos);
@@ -150,12 +158,14 @@
Slog.w(TAG, "IME uninstalled or not valid.: " + imiId);
continue;
}
+ final List<InputMethodSubtype> subtypesList = allSubtypes.get(imiId);
+ if (subtypesList == null) {
+ Slog.e(TAG, "Null subtype list for IME " + imiId);
+ continue;
+ }
out.startTag(null, NODE_IMI);
out.attribute(null, ATTR_ID, imiId);
- final List<InputMethodSubtype> subtypesList = allSubtypes.get(imiId);
- final int numSubtypes = subtypesList.size();
- for (int i = 0; i < numSubtypes; ++i) {
- final InputMethodSubtype subtype = subtypesList.get(i);
+ for (final InputMethodSubtype subtype : subtypesList) {
out.startTag(null, NODE_SUBTYPE);
if (subtype.hasSubtypeId()) {
out.attributeInt(null, ATTR_IME_SUBTYPE_ID, subtype.getSubtypeId());
@@ -163,6 +173,14 @@
out.attributeInt(null, ATTR_ICON, subtype.getIconResId());
out.attributeInt(null, ATTR_LABEL, subtype.getNameResId());
out.attribute(null, ATTR_NAME_OVERRIDE, subtype.getNameOverride().toString());
+ ULocale pkLanguageTag = subtype.getPhysicalKeyboardHintLanguageTag();
+ if (pkLanguageTag != null) {
+ out.attribute(null, ATTR_NAME_PK_LANGUAGE_TAG,
+ pkLanguageTag.toLanguageTag());
+ }
+ out.attribute(null, ATTR_NAME_PK_LAYOUT_TYPE,
+ subtype.getPhysicalKeyboardHintLayoutType());
+
out.attribute(null, ATTR_IME_SUBTYPE_LOCALE, subtype.getLocale());
out.attribute(null, ATTR_IME_SUBTYPE_LANGUAGE_TAG,
subtype.getLanguageTag());
@@ -203,19 +221,21 @@
allSubtypes.clear();
final AtomicFile subtypesFile = getAdditionalSubtypeFile(getInputMethodDir(userId));
- if (!subtypesFile.exists()) {
- // Not having the file means there is no additional subtype.
- return;
+ // Not having the file means there is no additional subtype.
+ if (subtypesFile.exists()) {
+ loadFromFile(allSubtypes, subtypesFile);
}
+ }
+
+ @VisibleForTesting
+ static void loadFromFile(@NonNull ArrayMap<String, List<InputMethodSubtype>> allSubtypes,
+ AtomicFile subtypesFile) {
try (FileInputStream fis = subtypesFile.openRead()) {
final TypedXmlPullParser parser = Xml.resolvePullParser(fis);
- int type = parser.getEventType();
+ int type = parser.next();
// Skip parsing until START_TAG
- while (true) {
+ while (type != XmlPullParser.START_TAG && type != XmlPullParser.END_DOCUMENT) {
type = parser.next();
- if (type == XmlPullParser.START_TAG || type == XmlPullParser.END_DOCUMENT) {
- break;
- }
}
String firstNodeName = parser.getName();
if (!NODE_SUBTYPES.equals(firstNodeName)) {
@@ -247,6 +267,10 @@
final int label = parser.getAttributeInt(null, ATTR_LABEL);
final String untranslatableName = parser.getAttributeValue(null,
ATTR_NAME_OVERRIDE);
+ final String pkLanguageTag = parser.getAttributeValue(null,
+ ATTR_NAME_PK_LANGUAGE_TAG);
+ final String pkLayoutType = parser.getAttributeValue(null,
+ ATTR_NAME_PK_LAYOUT_TYPE);
final String imeSubtypeLocale =
parser.getAttributeValue(null, ATTR_IME_SUBTYPE_LOCALE);
final String languageTag =
@@ -263,6 +287,9 @@
builder = new InputMethodSubtype.InputMethodSubtypeBuilder()
.setSubtypeNameResId(label)
.setSubtypeNameOverride(untranslatableName)
+ .setPhysicalKeyboardHint(
+ pkLanguageTag == null ? null : new ULocale(pkLanguageTag),
+ pkLayoutType == null ? "" : pkLayoutType)
.setSubtypeIconResId(icon)
.setSubtypeLocale(imeSubtypeLocale)
.setLanguageTag(languageTag)
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
index 080d582..97c8305 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
@@ -49,8 +49,11 @@
import static android.view.WindowManager.DISPLAY_IME_POLICY_HIDE;
import static android.view.WindowManager.DISPLAY_IME_POLICY_LOCAL;
+import static com.android.server.EventLogTags.IMF_HIDE_IME;
+import static com.android.server.EventLogTags.IMF_SHOW_IME;
import static com.android.server.inputmethod.InputMethodBindingController.TIME_TO_RECONNECT;
import static com.android.server.inputmethod.InputMethodUtils.isSoftInputModeStateVisibleAllowed;
+import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_IME_VISIBILITY;
import static java.lang.annotation.RetentionPolicy.SOURCE;
@@ -3439,6 +3442,12 @@
}
// TODO(b/192412909): Check if we can always call onShowHideSoftInputRequested() or not.
if (curMethod.showSoftInput(showInputToken, statsToken, showFlags, resultReceiver)) {
+ if (DEBUG_IME_VISIBILITY) {
+ EventLog.writeEvent(IMF_SHOW_IME, statsToken.getTag(),
+ Objects.toString(mCurFocusedWindow),
+ InputMethodDebug.softInputDisplayReasonToString(reason),
+ InputMethodDebug.softInputModeToString(mCurFocusedWindowSoftInputMode));
+ }
onShowHideSoftInputRequested(true /* show */, windowToken, reason);
}
mInputShown = true;
@@ -3537,6 +3546,12 @@
// TODO(b/192412909): Check if we can always call onShowHideSoftInputRequested() or not.
if (curMethod.hideSoftInput(hideInputToken, statsToken, 0 /* flags */,
resultReceiver)) {
+ if (DEBUG_IME_VISIBILITY) {
+ EventLog.writeEvent(IMF_HIDE_IME, statsToken.getTag(),
+ Objects.toString(mCurFocusedWindow),
+ InputMethodDebug.softInputDisplayReasonToString(reason),
+ InputMethodDebug.softInputModeToString(mCurFocusedWindowSoftInputMode));
+ }
onShowHideSoftInputRequested(false /* show */, windowToken, reason);
}
res = true;
diff --git a/services/core/java/com/android/server/locales/LocaleManagerService.java b/services/core/java/com/android/server/locales/LocaleManagerService.java
index 783a6ae..66c01dc 100644
--- a/services/core/java/com/android/server/locales/LocaleManagerService.java
+++ b/services/core/java/com/android/server/locales/LocaleManagerService.java
@@ -25,6 +25,7 @@
import android.app.ActivityManager;
import android.app.ActivityManagerInternal;
import android.app.ILocaleManager;
+import android.app.LocaleConfig;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
@@ -32,6 +33,7 @@
import android.content.pm.PackageManager.PackageInfoFlags;
import android.content.res.Configuration;
import android.os.Binder;
+import android.os.Environment;
import android.os.HandlerThread;
import android.os.LocaleList;
import android.os.Process;
@@ -42,21 +44,40 @@
import android.os.UserHandle;
import android.provider.Settings;
import android.text.TextUtils;
+import android.util.AtomicFile;
import android.util.Slog;
+import android.util.Xml;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.content.PackageMonitor;
import com.android.internal.util.FrameworkStatsLog;
+import com.android.internal.util.XmlUtils;
+import com.android.modules.utils.TypedXmlPullParser;
+import com.android.modules.utils.TypedXmlSerializer;
import com.android.server.LocalServices;
import com.android.server.SystemService;
import com.android.server.wm.ActivityTaskManagerInternal;
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.ByteArrayOutputStream;
+import java.io.File;
import java.io.FileDescriptor;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Locale;
/**
* The implementation of ILocaleManager.aidl.
*
- * <p>This service is API entry point for storing app-specific UI locales
+ * <p>This service is API entry point for storing app-specific UI locales and an override
+ * {@link LocaleConfig} for a specified app.
*/
public class LocaleManagerService extends SystemService {
private static final String TAG = "LocaleManagerService";
@@ -64,6 +85,13 @@
// app.
private static final String PROP_ALLOW_IME_QUERY_APP_LOCALE =
"i18n.feature.allow_ime_query_app_locale";
+ // The feature flag control that the application can dynamically override the LocaleConfig.
+ private static final String PROP_DYNAMIC_LOCALES_CHANGE =
+ "i18n.feature.dynamic_locales_change";
+ private static final String LOCALE_CONFIGS = "locale_configs";
+ private static final String SUFFIX_FILE_NAME = ".xml";
+ private static final String ATTR_NAME = "name";
+
final Context mContext;
private final LocaleManagerService.LocaleManagerBinderService mBinderService;
private ActivityTaskManagerInternal mActivityTaskManagerInternal;
@@ -74,6 +102,8 @@
private final PackageMonitor mPackageMonitor;
+ private final Object mWriteLock = new Object();
+
public static final boolean DEBUG = false;
public LocaleManagerService(Context context) {
@@ -103,7 +133,7 @@
new AppUpdateTracker(mContext, this, mBackupHelper);
mPackageMonitor = new LocaleManagerServicePackageMonitor(mBackupHelper,
- systemAppUpdateTracker, appUpdateTracker);
+ systemAppUpdateTracker, appUpdateTracker, this);
mPackageMonitor.register(context, broadcastHandlerThread.getLooper(),
UserHandle.ALL,
true);
@@ -173,6 +203,19 @@
}
@Override
+ public void setOverrideLocaleConfig(@NonNull String appPackageName, @UserIdInt int userId,
+ @Nullable LocaleConfig localeConfig) throws RemoteException {
+ LocaleManagerService.this.setOverrideLocaleConfig(appPackageName, userId, localeConfig);
+ }
+
+ @Override
+ @Nullable
+ public LocaleConfig getOverrideLocaleConfig(@NonNull String appPackageName,
+ @UserIdInt int userId) {
+ return LocaleManagerService.this.getOverrideLocaleConfig(appPackageName, userId);
+ }
+
+ @Override
public void onShellCommand(FileDescriptor in, FileDescriptor out,
FileDescriptor err, String[] args, ShellCallback callback,
ResultReceiver resultReceiver) {
@@ -211,7 +254,6 @@
if (!isCallerOwner) {
enforceChangeConfigurationPermission(atomRecordForMetrics);
}
-
mBackupHelper.persistLocalesModificationInfo(userId, appPackageName, fromDelegate,
locales.isEmpty());
final long token = Binder.clearCallingIdentity();
@@ -516,4 +558,242 @@
atomRecordForMetrics.mPrevLocales,
atomRecordForMetrics.mStatus);
}
+
+ /**
+ * Storing an override {@link LocaleConfig} for a specified app.
+ */
+ public void setOverrideLocaleConfig(@NonNull String appPackageName, @UserIdInt int userId,
+ @Nullable LocaleConfig localeConfig) throws IllegalArgumentException {
+ // TODO(b/262713398): Remove when stable
+ if (!SystemProperties.getBoolean(PROP_DYNAMIC_LOCALES_CHANGE, true)) {
+ return;
+ }
+
+ requireNonNull(appPackageName);
+
+ //Allow apps with INTERACT_ACROSS_USERS permission to set locales for different user.
+ userId = mActivityManagerInternal.handleIncomingUser(
+ Binder.getCallingPid(), Binder.getCallingUid(), userId,
+ false /* allowAll */, ActivityManagerInternal.ALLOW_NON_FULL,
+ "setOverrideLocaleConfig", /* callerPackage= */ null);
+
+ // This function handles two types of set operations:
+ // 1.) A normal, an app overrides its own LocaleConfig.
+ // 2.) A privileged system application or service is granted the necessary permission to
+ // override a LocaleConfig of another package.
+ if (!isPackageOwnedByCaller(appPackageName, userId)) {
+ enforceSetAppSpecificLocaleConfigPermission();
+ }
+
+ final long token = Binder.clearCallingIdentity();
+ try {
+ setOverrideLocaleConfigUnchecked(appPackageName, userId, localeConfig);
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+
+ //TODO: Add metrics to monitor the usage by applications
+ }
+ private void setOverrideLocaleConfigUnchecked(@NonNull String appPackageName,
+ @UserIdInt int userId, @Nullable LocaleConfig overridelocaleConfig) {
+ synchronized (mWriteLock) {
+ if (DEBUG) {
+ Slog.d(TAG,
+ "set the override LocaleConfig for package " + appPackageName + " and user "
+ + userId);
+ }
+ final File file = getXmlFileNameForUser(appPackageName, userId);
+
+ if (overridelocaleConfig == null) {
+ if (file.exists()) {
+ Slog.d(TAG, "remove the override LocaleConfig");
+ file.delete();
+ }
+ return;
+ } else {
+ LocaleList localeList = overridelocaleConfig.getSupportedLocales();
+ // Normally the LocaleList object should not be null. However we reassign it as the
+ // empty list in case it happens.
+ if (localeList == null) {
+ localeList = LocaleList.getEmptyLocaleList();
+ }
+ if (DEBUG) {
+ Slog.d(TAG,
+ "setOverrideLocaleConfig, localeList: " + localeList.toLanguageTags());
+ }
+
+ // Store the override LocaleConfig to the file storage.
+ final AtomicFile atomicFile = new AtomicFile(file);
+ FileOutputStream stream = null;
+ try {
+ stream = atomicFile.startWrite();
+ stream.write(toXmlByteArray(localeList));
+ } catch (Exception e) {
+ Slog.e(TAG, "Failed to write file " + atomicFile, e);
+ if (stream != null) {
+ atomicFile.failWrite(stream);
+ }
+ return;
+ }
+ atomicFile.finishWrite(stream);
+ // Clear per-app locales if they are not in the override LocaleConfig.
+ removeUnsupportedAppLocales(appPackageName, userId, overridelocaleConfig);
+ if (DEBUG) {
+ Slog.i(TAG, "Successfully written to " + atomicFile);
+ }
+ }
+ }
+ }
+
+ /**
+ * Checks if the per-app locales are in the new override LocaleConfig. Per-app locales
+ * missing from the new LocaleConfig will be removed.
+ */
+ private void removeUnsupportedAppLocales(String appPackageName, int userId,
+ LocaleConfig localeConfig) {
+ LocaleList appLocales = getApplicationLocalesUnchecked(appPackageName, userId);
+ // Remove the app locale from the locale list if it doesn't exist in the override
+ // LocaleConfig.
+ boolean resetAppLocales = false;
+ List<Locale> newAppLocales = new ArrayList<Locale>();
+ for (int i = 0; i < appLocales.size(); i++) {
+ if (!localeConfig.containsLocale(appLocales.get(i))) {
+ Slog.i(TAG, "reset the app locales");
+ resetAppLocales = true;
+ continue;
+ }
+ newAppLocales.add(appLocales.get(i));
+ }
+
+ if (resetAppLocales) {
+ // Reset the app locales
+ Locale[] locales = new Locale[newAppLocales.size()];
+ try {
+ setApplicationLocales(appPackageName, userId,
+ new LocaleList(newAppLocales.toArray(locales)), false);
+ } catch (RemoteException | IllegalArgumentException e) {
+ Slog.e(TAG, "Could not set locales for " + appPackageName, e);
+ }
+ }
+ }
+
+ private void enforceSetAppSpecificLocaleConfigPermission() {
+ mContext.enforceCallingOrSelfPermission(
+ android.Manifest.permission.SET_APP_SPECIFIC_LOCALECONFIG,
+ "setOverrideLocaleConfig");
+ }
+
+ /**
+ * Returns the override LocaleConfig for a specified app.
+ */
+ @Nullable
+ public LocaleConfig getOverrideLocaleConfig(@NonNull String appPackageName,
+ @UserIdInt int userId) {
+ // TODO(b/262713398): Remove when stable
+ if (!SystemProperties.getBoolean(PROP_DYNAMIC_LOCALES_CHANGE, true)) {
+ return null;
+ }
+
+ requireNonNull(appPackageName);
+
+ // Allow apps with INTERACT_ACROSS_USERS permission to query the override LocaleConfig for
+ // different user.
+ userId = mActivityManagerInternal.handleIncomingUser(
+ Binder.getCallingPid(), Binder.getCallingUid(), userId,
+ false /* allowAll */, ActivityManagerInternal.ALLOW_NON_FULL,
+ "getOverrideLocaleConfig", /* callerPackage= */ null);
+
+ final File file = getXmlFileNameForUser(appPackageName, userId);
+ if (!file.exists()) {
+ if (DEBUG) {
+ Slog.i(TAG, "getOverrideLocaleConfig, the file is not existed.");
+ }
+ return null;
+ }
+
+ try (InputStream in = new FileInputStream(file)) {
+ final TypedXmlPullParser parser = Xml.resolvePullParser(in);
+ List<String> overrideLocales = loadFromXml(parser);
+ if (DEBUG) {
+ Slog.i(TAG, "getOverrideLocaleConfig, Loaded locales: " + overrideLocales);
+ }
+ LocaleConfig storedLocaleConfig = new LocaleConfig(
+ LocaleList.forLanguageTags(String.join(",", overrideLocales)));
+
+ return storedLocaleConfig;
+ } catch (IOException | XmlPullParserException e) {
+ Slog.e(TAG, "Failed to parse XML configuration from " + file, e);
+ }
+
+ return null;
+ }
+
+ /**
+ * Delete an override {@link LocaleConfig} for a specified app from the file storage.
+ *
+ * <p>Clear the override LocaleConfig from the storage when the app is uninstalled.
+ */
+ void deleteOverrideLocaleConfig(@NonNull String appPackageName, @UserIdInt int userId) {
+ final File file = getXmlFileNameForUser(appPackageName, userId);
+
+ if (file.exists()) {
+ Slog.d(TAG, "Delete the override LocaleConfig.");
+ file.delete();
+ }
+ }
+
+ private byte[] toXmlByteArray(LocaleList localeList) {
+ try (ByteArrayOutputStream os = new ByteArrayOutputStream()) {
+ TypedXmlSerializer out = Xml.newFastSerializer();
+ out.setOutput(os, StandardCharsets.UTF_8.name());
+ out.startDocument(/* encoding= */ null, /* standalone= */ true);
+ out.startTag(/* namespace= */ null, LocaleConfig.TAG_LOCALE_CONFIG);
+
+ List<String> locales = new ArrayList<String>(
+ Arrays.asList(localeList.toLanguageTags().split(",")));
+ for (String locale : locales) {
+ out.startTag(null, LocaleConfig.TAG_LOCALE);
+ out.attribute(null, ATTR_NAME, locale);
+ out.endTag(null, LocaleConfig.TAG_LOCALE);
+ }
+
+ out.endTag(/* namespace= */ null, LocaleConfig.TAG_LOCALE_CONFIG);
+ out.endDocument();
+
+ if (DEBUG) {
+ Slog.d(TAG, "setOverrideLocaleConfig toXmlByteArray, output: " + os.toString());
+ }
+ return os.toByteArray();
+ } catch (IOException e) {
+ return null;
+ }
+ }
+
+ @NonNull
+ private List<String> loadFromXml(TypedXmlPullParser parser)
+ throws IOException, XmlPullParserException {
+ List<String> localeList = new ArrayList<>();
+
+ XmlUtils.beginDocument(parser, LocaleConfig.TAG_LOCALE_CONFIG);
+ int depth = parser.getDepth();
+ while (XmlUtils.nextElementWithin(parser, depth)) {
+ final String tagName = parser.getName();
+ if (LocaleConfig.TAG_LOCALE.equals(tagName)) {
+ String locale = parser.getAttributeValue(/* namespace= */ null, ATTR_NAME);
+ localeList.add(locale);
+ } else {
+ Slog.w(TAG, "Unexpected tag name: " + tagName);
+ XmlUtils.skipCurrentTag(parser);
+ }
+ }
+
+ return localeList;
+ }
+
+ @NonNull
+ private File getXmlFileNameForUser(@NonNull String appPackageName, @UserIdInt int userId) {
+ // TODO(b/262752965): use per-package data directory
+ final File dir = new File(Environment.getDataSystemDeDirectory(userId), LOCALE_CONFIGS);
+ return new File(dir, appPackageName + SUFFIX_FILE_NAME);
+ }
}
diff --git a/services/core/java/com/android/server/locales/LocaleManagerServicePackageMonitor.java b/services/core/java/com/android/server/locales/LocaleManagerServicePackageMonitor.java
index 1a38f0c..771e1b0 100644
--- a/services/core/java/com/android/server/locales/LocaleManagerServicePackageMonitor.java
+++ b/services/core/java/com/android/server/locales/LocaleManagerServicePackageMonitor.java
@@ -16,6 +16,9 @@
package com.android.server.locales;
+import android.annotation.NonNull;
+import android.os.UserHandle;
+
import com.android.internal.content.PackageMonitor;
/**
@@ -35,12 +38,16 @@
private LocaleManagerBackupHelper mBackupHelper;
private SystemAppUpdateTracker mSystemAppUpdateTracker;
private AppUpdateTracker mAppUpdateTracker;
+ private LocaleManagerService mLocaleManagerService;
- LocaleManagerServicePackageMonitor(LocaleManagerBackupHelper localeManagerBackupHelper,
- SystemAppUpdateTracker systemAppUpdateTracker, AppUpdateTracker appUpdateTracker) {
+ LocaleManagerServicePackageMonitor(@NonNull LocaleManagerBackupHelper localeManagerBackupHelper,
+ @NonNull SystemAppUpdateTracker systemAppUpdateTracker,
+ @NonNull AppUpdateTracker appUpdateTracker,
+ @NonNull LocaleManagerService localeManagerService) {
mBackupHelper = localeManagerBackupHelper;
mSystemAppUpdateTracker = systemAppUpdateTracker;
mAppUpdateTracker = appUpdateTracker;
+ mLocaleManagerService = localeManagerService;
}
@Override
@@ -56,6 +63,7 @@
@Override
public void onPackageRemoved(String packageName, int uid) {
mBackupHelper.onPackageRemoved(packageName, uid);
+ mLocaleManagerService.deleteOverrideLocaleConfig(packageName, UserHandle.getUserId(uid));
}
@Override
diff --git a/services/core/java/com/android/server/locales/LocaleManagerShellCommand.java b/services/core/java/com/android/server/locales/LocaleManagerShellCommand.java
index c5069e5..09f2ffa 100644
--- a/services/core/java/com/android/server/locales/LocaleManagerShellCommand.java
+++ b/services/core/java/com/android/server/locales/LocaleManagerShellCommand.java
@@ -18,6 +18,7 @@
import android.app.ActivityManager;
import android.app.ILocaleManager;
+import android.app.LocaleConfig;
import android.os.LocaleList;
import android.os.RemoteException;
import android.os.ShellCommand;
@@ -44,6 +45,10 @@
return runSetAppLocales();
case "get-app-locales":
return runGetAppLocales();
+ case "set-app-localeconfig":
+ return runSetAppOverrideLocaleConfig();
+ case "get-app-localeconfig":
+ return runGetAppOverrideLocaleConfig();
default: {
return handleDefaultCommands(cmd);
}
@@ -62,15 +67,30 @@
pw.println(" --user <USER_ID>: apply for the given user, "
+ "the current user is used when unspecified.");
pw.println(" --locales <LOCALE_INFO>: The language tags of locale to be included "
- + "as a single String separated by commas");
- pw.println(" Empty locale list is used when unspecified.");
+ + "as a single String separated by commas.");
pw.println(" eg. en,en-US,hi ");
+ pw.println(" Empty locale list is used when unspecified.");
pw.println(" --delegate <FROM_DELEGATE>: The locales are set from a delegate, "
+ "the value could be true or false. false is the default when unspecified.");
pw.println(" get-app-locales <PACKAGE_NAME> [--user <USER_ID>]");
pw.println(" Get the locales for the specified app.");
pw.println(" --user <USER_ID>: get for the given user, "
+ "the current user is used when unspecified.");
+ pw.println(
+ " set-app-localeconfig <PACKAGE_NAME> [--user <USER_ID>] [--locales "
+ + "<LOCALE_INFO>]");
+ pw.println(" Set the override LocaleConfig for the specified app.");
+ pw.println(" --user <USER_ID>: apply for the given user, "
+ + "the current user is used when unspecified.");
+ pw.println(" --locales <LOCALE_INFO>: The language tags of locale to be included "
+ + "as a single String separated by commas.");
+ pw.println(" eg. en,en-US,hi ");
+ pw.println(" Empty locale list is used when typing a 'empty' word");
+ pw.println(" NULL is used when unspecified.");
+ pw.println(" get-app-localeconfig <PACKAGE_NAME> [--user <USER_ID>]");
+ pw.println(" Get the locales within the override LocaleConfig for the specified app.");
+ pw.println(" --user <USER_ID>: get for the given user, "
+ + "the current user is used when unspecified.");
}
private int runSetAppLocales() {
@@ -155,6 +175,106 @@
return 0;
}
+ private int runSetAppOverrideLocaleConfig() {
+ String packageName = getNextArg();
+
+ if (packageName != null) {
+ int userId = ActivityManager.getCurrentUser();
+ LocaleList locales = null;
+ do {
+ String option = getNextOption();
+ if (option == null) {
+ break;
+ }
+ switch (option) {
+ case "--user": {
+ userId = UserHandle.parseUserArg(getNextArgRequired());
+ break;
+ }
+ case "--locales": {
+ locales = parseOverrideLocales();
+ break;
+ }
+ default: {
+ throw new IllegalArgumentException("Unknown option: " + option);
+ }
+ }
+ } while (true);
+
+ try {
+ LocaleConfig localeConfig = locales == null ? null : new LocaleConfig(locales);
+ mBinderService.setOverrideLocaleConfig(packageName, userId, localeConfig);
+ } catch (RemoteException e) {
+ getOutPrintWriter().println("Remote Exception: " + e);
+ }
+ } else {
+ final PrintWriter err = getErrPrintWriter();
+ err.println("Error: no package specified");
+ return -1;
+ }
+ return 0;
+ }
+
+ private int runGetAppOverrideLocaleConfig() {
+ String packageName = getNextArg();
+
+ if (packageName != null) {
+ int userId = ActivityManager.getCurrentUser();
+ do {
+ String option = getNextOption();
+ if (option == null) {
+ break;
+ }
+ if ("--user".equals(option)) {
+ userId = UserHandle.parseUserArg(getNextArgRequired());
+ break;
+ } else {
+ throw new IllegalArgumentException("Unknown option: " + option);
+ }
+ } while (true);
+ try {
+ LocaleConfig localeConfig = mBinderService.getOverrideLocaleConfig(packageName,
+ userId);
+ if (localeConfig == null) {
+ getOutPrintWriter().println("LocaleConfig for " + packageName
+ + " for user " + userId + " is null");
+ } else {
+ LocaleList locales = localeConfig.getSupportedLocales();
+ if (locales == null) {
+ getOutPrintWriter().println(
+ "Locales within the LocaleConfig for " + packageName + " for user "
+ + userId + " are null");
+ } else {
+ getOutPrintWriter().println(
+ "Locales within the LocaleConfig for " + packageName + " for user "
+ + userId + " are [" + locales.toLanguageTags() + "]");
+ }
+ }
+ } catch (RemoteException e) {
+ getOutPrintWriter().println("Remote Exception: " + e);
+ }
+ } else {
+ final PrintWriter err = getErrPrintWriter();
+ err.println("Error: no package specified");
+ return -1;
+ }
+ return 0;
+ }
+
+ private LocaleList parseOverrideLocales() {
+ String locales = getNextArg();
+ if (locales == null) {
+ return null;
+ } else if (locales.equals("empty")) {
+ return LocaleList.getEmptyLocaleList();
+ } else {
+ if (locales.startsWith("-")) {
+ throw new IllegalArgumentException("Unknown locales: " + locales);
+ }
+ return LocaleList.forLanguageTags(locales);
+ }
+ }
+
private LocaleList parseLocales() {
String locales = getNextArg();
if (locales == null) {
diff --git a/services/core/java/com/android/server/locksettings/LockSettingsService.java b/services/core/java/com/android/server/locksettings/LockSettingsService.java
index 5f39a52..90f2a6a 100644
--- a/services/core/java/com/android/server/locksettings/LockSettingsService.java
+++ b/services/core/java/com/android/server/locksettings/LockSettingsService.java
@@ -883,7 +883,6 @@
* Migrate the credential for the FRP credential owner user if the following are satisfied:
* - the user has a secure credential
* - the FRP credential is not set up
- * - the credential is based on a synthetic password.
*/
private void migrateFrpCredential() {
if (mStorage.readPersistentDataBlock() != PersistentData.NONE) {
@@ -892,15 +891,13 @@
for (UserInfo userInfo : mUserManager.getUsers()) {
if (userOwnsFrpCredential(mContext, userInfo) && isUserSecure(userInfo.id)) {
synchronized (mSpManager) {
- if (isSyntheticPasswordBasedCredentialLocked(userInfo.id)) {
- int actualQuality = (int) getLong(LockPatternUtils.PASSWORD_TYPE_KEY,
- DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED, userInfo.id);
+ int actualQuality = (int) getLong(LockPatternUtils.PASSWORD_TYPE_KEY,
+ DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED, userInfo.id);
- mSpManager.migrateFrpPasswordLocked(
- getCurrentLskfBasedProtectorId(userInfo.id),
- userInfo,
- redactActualQualityToMostLenientEquivalentQuality(actualQuality));
- }
+ mSpManager.migrateFrpPasswordLocked(
+ getCurrentLskfBasedProtectorId(userInfo.id),
+ userInfo,
+ redactActualQualityToMostLenientEquivalentQuality(actualQuality));
}
return;
}
@@ -941,13 +938,9 @@
int serialNumber = mEarlyCreatedUsers.valueAt(i);
removeStateForReusedUserIdIfNecessary(userId, serialNumber);
- synchronized (mSpManager) {
- if (!isSyntheticPasswordBasedCredentialLocked(userId)) {
- Slogf.i(TAG, "Creating locksettings state for user %d now that boot "
- + "is complete", userId);
- initializeSyntheticPassword(userId);
- }
- }
+ Slogf.i(TAG, "Creating locksettings state for user %d now that boot is complete",
+ userId);
+ initializeSyntheticPassword(userId);
}
mEarlyCreatedUsers = null; // no longer needed
@@ -1234,16 +1227,17 @@
return getFrpCredentialType();
}
synchronized (mSpManager) {
- if (isSyntheticPasswordBasedCredentialLocked(userId)) {
- final long protectorId = getCurrentLskfBasedProtectorId(userId);
- int rawType = mSpManager.getCredentialType(protectorId, userId);
- if (rawType != CREDENTIAL_TYPE_PASSWORD_OR_PIN) {
- return rawType;
- }
- return pinOrPasswordQualityToCredentialType(getKeyguardStoredQuality(userId));
+ final long protectorId = getCurrentLskfBasedProtectorId(userId);
+ if (protectorId == SyntheticPasswordManager.NULL_PROTECTOR_ID) {
+ // Only possible for new users during early boot (before onThirdPartyAppsStarted())
+ return CREDENTIAL_TYPE_NONE;
}
+ int rawType = mSpManager.getCredentialType(protectorId, userId);
+ if (rawType != CREDENTIAL_TYPE_PASSWORD_OR_PIN) {
+ return rawType;
+ }
+ return pinOrPasswordQualityToCredentialType(getKeyguardStoredQuality(userId));
}
- return CREDENTIAL_TYPE_NONE;
}
private int getFrpCredentialType() {
@@ -2167,10 +2161,6 @@
VerifyCredentialResponse response;
synchronized (mSpManager) {
- if (!isSyntheticPasswordBasedCredentialLocked(userId)) {
- Slog.wtf(TAG, "Unexpected credential type, should be SP based.");
- return VerifyCredentialResponse.ERROR;
- }
if (userId == USER_FRP) {
return mSpManager.verifyFrpCredential(getGateKeeperService(), credential,
progressCallback);
@@ -2424,16 +2414,20 @@
public void onShellCommand(FileDescriptor in, FileDescriptor out, FileDescriptor err,
String[] args, ShellCallback callback, ResultReceiver resultReceiver) {
enforceShell();
- final int origPid = Binder.getCallingPid();
- final int origUid = Binder.getCallingUid();
+ final int callingPid = Binder.getCallingPid();
+ final int callingUid = Binder.getCallingUid();
- Slog.e(TAG, "Caller pid " + origPid + " Caller uid " + origUid);
+ // Don't log arguments other than the first one (the command name), since they might contain
+ // secrets that must not be written to the log.
+ Slogf.i(TAG, "Executing shell command '%s'; callingPid=%d, callingUid=%d",
+ ArrayUtils.isEmpty(args) ? "" : args[0], callingPid, callingUid);
+
// The original identity is an opaque integer.
final long origId = Binder.clearCallingIdentity();
try {
final LockSettingsShellCommand command =
- new LockSettingsShellCommand(new LockPatternUtils(mContext), mContext, origPid,
- origUid);
+ new LockSettingsShellCommand(new LockPatternUtils(mContext), mContext,
+ callingPid, callingUid);
command.exec(this, in, out, err, args, callback, resultReceiver);
} finally {
Binder.restoreCallingIdentity(origId);
@@ -2672,15 +2666,6 @@
setLong(LSKF_LAST_CHANGED_TIME_KEY, System.currentTimeMillis(), userId);
}
- private boolean isSyntheticPasswordBasedCredentialLocked(int userId) {
- if (userId == USER_FRP) {
- final int type = mStorage.readPersistentDataBlock().type;
- return type == PersistentData.TYPE_SP || type == PersistentData.TYPE_SP_WEAVER;
- }
- long protectorId = getCurrentLskfBasedProtectorId(userId);
- return protectorId != SyntheticPasswordManager.NULL_PROTECTOR_ID;
- }
-
/**
* Stores the gatekeeper password temporarily.
* @param gatekeeperPassword unlocked upon successful Synthetic Password
@@ -2888,10 +2873,6 @@
}
}
synchronized (mSpManager) {
- if (!isSyntheticPasswordBasedCredentialLocked(userId)) {
- Slog.w(TAG, "Synthetic password not enabled");
- return null;
- }
long protectorId = getCurrentLskfBasedProtectorId(userId);
AuthenticationResult auth = mSpManager.unlockLskfBasedProtector(
getGateKeeperService(), protectorId, currentCredential, userId, null);
@@ -3218,9 +3199,7 @@
// Disable escrow token permanently on all other device/user types.
Slog.i(TAG, "Disabling escrow token on user " + userId);
- if (isSyntheticPasswordBasedCredentialLocked(userId)) {
- mSpManager.destroyEscrowData(userId);
- }
+ mSpManager.destroyEscrowData(userId);
}
/**
diff --git a/services/core/java/com/android/server/locksettings/SyntheticPasswordManager.java b/services/core/java/com/android/server/locksettings/SyntheticPasswordManager.java
index acd7cc1..66ce429 100644
--- a/services/core/java/com/android/server/locksettings/SyntheticPasswordManager.java
+++ b/services/core/java/com/android/server/locksettings/SyntheticPasswordManager.java
@@ -24,13 +24,14 @@
import android.app.admin.PasswordMetrics;
import android.content.Context;
import android.content.pm.UserInfo;
-import android.hardware.weaver.V1_0.IWeaver;
-import android.hardware.weaver.V1_0.WeaverConfig;
-import android.hardware.weaver.V1_0.WeaverReadResponse;
-import android.hardware.weaver.V1_0.WeaverReadStatus;
-import android.hardware.weaver.V1_0.WeaverStatus;
+import android.hardware.weaver.IWeaver;
+import android.hardware.weaver.WeaverConfig;
+import android.hardware.weaver.WeaverReadResponse;
+import android.hardware.weaver.WeaverReadStatus;
import android.os.RemoteCallbackList;
import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.os.ServiceSpecificException;
import android.os.UserManager;
import android.provider.Settings;
import android.security.GateKeeper;
@@ -60,7 +61,6 @@
import java.nio.ByteBuffer;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
-import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
@@ -464,37 +464,67 @@
}
@VisibleForTesting
- protected IWeaver getWeaverService() throws RemoteException {
+ protected android.hardware.weaver.V1_0.IWeaver getWeaverHidlService() throws RemoteException {
try {
- return IWeaver.getService(/* retry */ true);
+ return android.hardware.weaver.V1_0.IWeaver.getService(/* retry */ true);
} catch (NoSuchElementException e) {
- Slog.i(TAG, "Device does not support weaver");
return null;
}
}
+ private IWeaver getWeaverService() {
+ // Try to get the AIDL service first
+ try {
+ IWeaver aidlWeaver = IWeaver.Stub.asInterface(
+ ServiceManager.waitForDeclaredService(IWeaver.DESCRIPTOR + "/default"));
+ if (aidlWeaver != null) {
+ Slog.i(TAG, "Using AIDL weaver service");
+ return aidlWeaver;
+ }
+ } catch (SecurityException e) {
+ Slog.w(TAG, "Does not have permissions to get AIDL weaver service");
+ }
+
+ // If the AIDL service can't be found, look for the HIDL service
+ try {
+ android.hardware.weaver.V1_0.IWeaver hidlWeaver = getWeaverHidlService();
+ if (hidlWeaver != null) {
+ Slog.i(TAG, "Using HIDL weaver service");
+ return new WeaverHidlWrapper(hidlWeaver);
+ }
+ } catch (RemoteException e) {
+ Slog.w(TAG, "Failed to get HIDL weaver service.", e);
+ }
+ Slog.w(TAG, "Device does not support weaver");
+ return null;
+ }
+
public synchronized void initWeaverService() {
if (mWeaver != null) {
return;
}
- try {
- mWeaverConfig = null;
- mWeaver = getWeaverService();
- if (mWeaver != null) {
- mWeaver.getConfig((int status, WeaverConfig config) -> {
- if (status == WeaverStatus.OK && config.slots > 0) {
- mWeaverConfig = config;
- } else {
- Slog.e(TAG, "Failed to get weaver config, status " + status
- + " slots: " + config.slots);
- mWeaver = null;
- }
- });
- mPasswordSlotManager.refreshActiveSlots(getUsedWeaverSlots());
- }
- } catch (RemoteException e) {
- Slog.e(TAG, "Failed to get weaver service", e);
+
+ IWeaver weaver = getWeaverService();
+ if (weaver == null) {
+ return;
}
+
+ // Get the config
+ WeaverConfig weaverConfig = null;
+ try {
+ weaverConfig = weaver.getConfig();
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Failed to get weaver config", e);
+ }
+ if (weaverConfig == null || weaverConfig.slots <= 0) {
+ Slog.e(TAG, "Failed to initialize weaver config");
+ return;
+ }
+
+ mWeaver = weaver;
+ mWeaverConfig = weaverConfig;
+ mPasswordSlotManager.refreshActiveSlots(getUsedWeaverSlots());
+ Slog.i(TAG, "Weaver service initialized");
}
private synchronized boolean isWeaverAvailable() {
@@ -525,19 +555,31 @@
value = secureRandom(mWeaverConfig.valueSize);
}
try {
- int writeStatus = mWeaver.write(slot, toByteArrayList(key), toByteArrayList(value));
- if (writeStatus != WeaverStatus.OK) {
- Slog.e(TAG, "weaver write failed, slot: " + slot + " status: " + writeStatus);
- return null;
- }
+ mWeaver.write(slot, key, value);
} catch (RemoteException e) {
- Slog.e(TAG, "weaver write failed", e);
+ Slog.e(TAG, "weaver write binder call failed, slot: " + slot, e);
+ return null;
+ } catch (ServiceSpecificException e) {
+ Slog.e(TAG, "weaver write failed, slot: " + slot, e);
return null;
}
return value;
}
/**
+ * Create a VerifyCredentialResponse from a timeout base on the WeaverReadResponse.
+ * This checks the received timeout(long) to make sure it sure it fits in an int before
+ * using it. If it doesn't fit, we use Integer.MAX_VALUE.
+ */
+ private static VerifyCredentialResponse responseFromTimeout(WeaverReadResponse response) {
+ int timeout =
+ response.timeout > Integer.MAX_VALUE || response.timeout < 0
+ ? Integer.MAX_VALUE
+ : (int) response.timeout;
+ return VerifyCredentialResponse.fromTimeout(timeout);
+ }
+
+ /**
* Verify the supplied key against a weaver slot, returning a response indicating whether
* the verification is successful, throttled or failed. If successful, the bound secret
* is also returned.
@@ -551,46 +593,39 @@
} else if (key.length != mWeaverConfig.keySize) {
throw new IllegalArgumentException("Invalid key size for weaver");
}
- final VerifyCredentialResponse[] response = new VerifyCredentialResponse[1];
+ final WeaverReadResponse readResponse;
try {
- mWeaver.read(slot, toByteArrayList(key),
- (int status, WeaverReadResponse readResponse) -> {
- switch (status) {
- case WeaverReadStatus.OK:
- response[0] = new VerifyCredentialResponse.Builder().setGatekeeperHAT(
- fromByteArrayList(readResponse.value)).build();
- break;
- case WeaverReadStatus.THROTTLE:
- response[0] = VerifyCredentialResponse
- .fromTimeout(readResponse.timeout);
- Slog.e(TAG, "weaver read failed (THROTTLE), slot: " + slot);
- break;
- case WeaverReadStatus.INCORRECT_KEY:
- if (readResponse.timeout == 0) {
- response[0] = VerifyCredentialResponse.ERROR;
- Slog.e(TAG, "weaver read failed (INCORRECT_KEY), slot: " + slot);
- } else {
- response[0] = VerifyCredentialResponse
- .fromTimeout(readResponse.timeout);
- Slog.e(TAG, "weaver read failed (INCORRECT_KEY/THROTTLE), slot: "
- + slot);
- }
- break;
- case WeaverReadStatus.FAILED:
- response[0] = VerifyCredentialResponse.ERROR;
- Slog.e(TAG, "weaver read failed (FAILED), slot: " + slot);
- break;
- default:
- response[0] = VerifyCredentialResponse.ERROR;
- Slog.e(TAG, "weaver read unknown status " + status + ", slot: " + slot);
- break;
- }
- });
+ readResponse = mWeaver.read(slot, key);
} catch (RemoteException e) {
- response[0] = VerifyCredentialResponse.ERROR;
Slog.e(TAG, "weaver read failed, slot: " + slot, e);
+ return VerifyCredentialResponse.ERROR;
}
- return response[0];
+
+ switch (readResponse.status) {
+ case WeaverReadStatus.OK:
+ return new VerifyCredentialResponse.Builder()
+ .setGatekeeperHAT(readResponse.value)
+ .build();
+ case WeaverReadStatus.THROTTLE:
+ Slog.e(TAG, "weaver read failed (THROTTLE), slot: " + slot);
+ return responseFromTimeout(readResponse);
+ case WeaverReadStatus.INCORRECT_KEY:
+ if (readResponse.timeout == 0) {
+ Slog.e(TAG, "weaver read failed (INCORRECT_KEY), slot: " + slot);
+ return VerifyCredentialResponse.ERROR;
+ } else {
+ Slog.e(TAG, "weaver read failed (INCORRECT_KEY/THROTTLE), slot: " + slot);
+ return responseFromTimeout(readResponse);
+ }
+ case WeaverReadStatus.FAILED:
+ Slog.e(TAG, "weaver read failed (FAILED), slot: " + slot);
+ return VerifyCredentialResponse.ERROR;
+ default:
+ Slog.e(TAG,
+ "weaver read unknown status " + readResponse.status
+ + ", slot: " + slot);
+ return VerifyCredentialResponse.ERROR;
+ }
}
public void removeUser(IGateKeeperService gatekeeper, int userId) {
@@ -1660,22 +1695,6 @@
native long nativeSidFromPasswordHandle(byte[] handle);
- protected static ArrayList<Byte> toByteArrayList(byte[] data) {
- ArrayList<Byte> result = new ArrayList<Byte>(data.length);
- for (int i = 0; i < data.length; i++) {
- result.add(data[i]);
- }
- return result;
- }
-
- protected static byte[] fromByteArrayList(ArrayList<Byte> data) {
- byte[] result = new byte[data.size()];
- for (int i = 0; i < data.size(); i++) {
- result[i] = data.get(i);
- }
- return result;
- }
-
@VisibleForTesting
static byte[] bytesToHex(byte[] bytes) {
return HexEncoding.encodeToString(bytes).getBytes();
diff --git a/services/core/java/com/android/server/locksettings/WeaverHidlWrapper.java b/services/core/java/com/android/server/locksettings/WeaverHidlWrapper.java
new file mode 100644
index 0000000..9d93c3d
--- /dev/null
+++ b/services/core/java/com/android/server/locksettings/WeaverHidlWrapper.java
@@ -0,0 +1,143 @@
+/*
+ * Copyright (C) 2022 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.locksettings;
+
+import android.hardware.weaver.V1_0.IWeaver;
+import android.hardware.weaver.V1_0.WeaverConfig;
+import android.hardware.weaver.V1_0.WeaverReadResponse;
+import android.hardware.weaver.V1_0.WeaverReadStatus;
+import android.hardware.weaver.V1_0.WeaverStatus;
+import android.os.RemoteException;
+import android.os.ServiceSpecificException;
+import android.util.Slog;
+
+import java.util.ArrayList;
+
+/**
+ * Implement the AIDL IWeaver interface wrapping the HIDL implementation
+ */
+class WeaverHidlWrapper implements android.hardware.weaver.IWeaver {
+ private static final String TAG = "WeaverHidlWrapper";
+ private final IWeaver mImpl;
+
+ WeaverHidlWrapper(IWeaver impl) {
+ mImpl = impl;
+ }
+
+ private static ArrayList<Byte> toByteArrayList(byte[] data) {
+ ArrayList<Byte> result = new ArrayList<Byte>(data.length);
+ for (int i = 0; i < data.length; i++) {
+ result.add(data[i]);
+ }
+ return result;
+ }
+
+ private static byte[] fromByteArrayList(ArrayList<Byte> data) {
+ byte[] result = new byte[data.size()];
+ for (int i = 0; i < data.size(); i++) {
+ result[i] = data.get(i);
+ }
+ return result;
+ }
+
+ @Override
+ public String getInterfaceHash() {
+ // We do not require the interface hash as the client.
+ throw new UnsupportedOperationException(
+ "WeaverHidlWrapper does not support getInterfaceHash");
+ }
+ @Override
+ public int getInterfaceVersion() {
+ // Supports only V2 which is at feature parity.
+ return 2;
+ }
+ @Override
+ public android.os.IBinder asBinder() {
+ // There is no IHwBinder to IBinder. Not required as the client.
+ throw new UnsupportedOperationException("WeaverHidlWrapper does not support asBinder");
+ }
+
+ @Override
+ public android.hardware.weaver.WeaverConfig getConfig() throws RemoteException {
+ final WeaverConfig[] res = new WeaverConfig[1];
+ mImpl.getConfig((int status, WeaverConfig config) -> {
+ if (status == WeaverStatus.OK && config.slots > 0) {
+ res[0] = config;
+ } else {
+ res[0] = null;
+ Slog.e(TAG,
+ "Failed to get HIDL weaver config. status: " + status
+ + ", slots: " + config.slots);
+ }
+ });
+
+ if (res[0] == null) {
+ return null;
+ }
+ android.hardware.weaver.WeaverConfig config = new android.hardware.weaver.WeaverConfig();
+ config.slots = res[0].slots;
+ config.keySize = res[0].keySize;
+ config.valueSize = res[0].valueSize;
+ return config;
+ }
+
+ @Override
+ public android.hardware.weaver.WeaverReadResponse read(int slotId, byte[] key)
+ throws RemoteException {
+ final WeaverReadResponse[] res = new WeaverReadResponse[1];
+ final int[] status = new int[1];
+ mImpl.read(
+ slotId, toByteArrayList(key), (int inStatus, WeaverReadResponse readResponse) -> {
+ status[0] = inStatus;
+ res[0] = readResponse;
+ });
+
+ android.hardware.weaver.WeaverReadResponse aidlRes =
+ new android.hardware.weaver.WeaverReadResponse();
+ switch (status[0]) {
+ case WeaverReadStatus.OK:
+ aidlRes.status = android.hardware.weaver.WeaverReadStatus.OK;
+ break;
+ case WeaverReadStatus.THROTTLE:
+ aidlRes.status = android.hardware.weaver.WeaverReadStatus.THROTTLE;
+ break;
+ case WeaverReadStatus.INCORRECT_KEY:
+ aidlRes.status = android.hardware.weaver.WeaverReadStatus.INCORRECT_KEY;
+ break;
+ case WeaverReadStatus.FAILED:
+ aidlRes.status = android.hardware.weaver.WeaverReadStatus.FAILED;
+ break;
+ default:
+ aidlRes.status = android.hardware.weaver.WeaverReadStatus.FAILED;
+ break;
+ }
+ if (res[0] != null) {
+ aidlRes.timeout = res[0].timeout;
+ aidlRes.value = fromByteArrayList(res[0].value);
+ }
+ return aidlRes;
+ }
+
+ @Override
+ public void write(int slotId, byte[] key, byte[] value) throws RemoteException {
+ int writeStatus = mImpl.write(slotId, toByteArrayList(key), toByteArrayList(value));
+ if (writeStatus != WeaverStatus.OK) {
+ throw new ServiceSpecificException(
+ android.hardware.weaver.IWeaver.STATUS_FAILED, "Failed IWeaver.write call");
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/logcat/TEST_MAPPING b/services/core/java/com/android/server/logcat/TEST_MAPPING
new file mode 100644
index 0000000..f4b13a0
--- /dev/null
+++ b/services/core/java/com/android/server/logcat/TEST_MAPPING
@@ -0,0 +1,21 @@
+{
+ "presubmit": [
+ {
+ "name": "FrameworksServicesTests",
+ "options": [
+ {"include-filter": "com.android.server.logcat"},
+ {"exclude-annotation": "android.platform.test.annotations.FlakyTest"},
+ {"exclude-annotation": "androidx.test.filters.FlakyTest"}
+ ]
+ }
+ ],
+ "postsubmit": [
+ {
+ "name": "FrameworksServicesTests",
+ "options": [
+ {"include-filter": "com.android.server.logcat"}
+ ]
+ }
+ ]
+}
+
diff --git a/services/core/java/com/android/server/pm/AppStateHelper.java b/services/core/java/com/android/server/pm/AppStateHelper.java
index e6e8212a..2ef193c 100644
--- a/services/core/java/com/android/server/pm/AppStateHelper.java
+++ b/services/core/java/com/android/server/pm/AppStateHelper.java
@@ -16,16 +16,18 @@
package com.android.server.pm;
-import static android.media.AudioAttributes.USAGE_VOICE_COMMUNICATION;
-import static android.media.AudioAttributes.USAGE_VOICE_COMMUNICATION_SIGNALLING;
-
import android.app.ActivityManager;
import android.app.ActivityManager.RunningAppProcessInfo;
import android.app.ActivityManagerInternal;
+import android.app.ActivityThread;
+import android.app.usage.NetworkStats;
+import android.app.usage.NetworkStatsManager;
import android.content.Context;
import android.media.AudioManager;
import android.media.IAudioService;
+import android.net.ConnectivityManager;
import android.os.ServiceManager;
+import android.os.SystemProperties;
import android.telecom.TelecomManager;
import android.text.TextUtils;
import android.util.ArraySet;
@@ -35,11 +37,15 @@
import java.util.ArrayList;
import java.util.List;
+import java.util.concurrent.TimeUnit;
/**
* A helper class to provide queries for app states concerning gentle-update.
*/
public class AppStateHelper {
+ // The duration to monitor network usage to determine if network is active or not
+ private static final long ACTIVE_NETWORK_DURATION_MILLIS = TimeUnit.MINUTES.toMillis(10);
+
private final Context mContext;
public AppStateHelper(Context context) {
@@ -84,19 +90,12 @@
private boolean hasVoiceCall() {
var am = mContext.getSystemService(AudioManager.class);
try {
- for (var apc : am.getActivePlaybackConfigurations()) {
- if (!apc.isActive()) {
- continue;
- }
- var usage = apc.getAudioAttributes().getUsage();
- if (usage == USAGE_VOICE_COMMUNICATION
- || usage == USAGE_VOICE_COMMUNICATION_SIGNALLING) {
- return true;
- }
- }
+ int audioMode = am.getMode();
+ return audioMode == AudioManager.MODE_IN_CALL
+ || audioMode == AudioManager.MODE_IN_COMMUNICATION;
} catch (Exception ignore) {
+ return false;
}
- return false;
}
/**
@@ -136,26 +135,48 @@
return hasAudioFocus(packageName) || isRecordingAudio(packageName);
}
- /**
- * True if the app is sending or receiving network data.
- */
- private boolean hasActiveNetwork(String packageName) {
- // To be implemented
+ private boolean hasActiveNetwork(List<String> packageNames, int networkType) {
+ var pm = ActivityThread.getPackageManager();
+ var nsm = mContext.getSystemService(NetworkStatsManager.class);
+ var endTime = System.currentTimeMillis();
+ var startTime = endTime - ACTIVE_NETWORK_DURATION_MILLIS;
+ try (var stats = nsm.querySummary(networkType, null, startTime, endTime)) {
+ var bucket = new NetworkStats.Bucket();
+ while (stats.hasNextBucket()) {
+ stats.getNextBucket(bucket);
+ var packageName = pm.getNameForUid(bucket.getUid());
+ if (!packageNames.contains(packageName)) {
+ continue;
+ }
+ if (bucket.getRxPackets() > 0 || bucket.getTxPackets() > 0) {
+ return true;
+ }
+ }
+ } catch (Exception ignore) {
+ }
return false;
}
/**
+ * True if any app has sent or received network data over the past
+ * {@link #ACTIVE_NETWORK_DURATION_MILLIS} milliseconds.
+ */
+ private boolean hasActiveNetwork(List<String> packageNames) {
+ return hasActiveNetwork(packageNames, ConnectivityManager.TYPE_WIFI)
+ || hasActiveNetwork(packageNames, ConnectivityManager.TYPE_MOBILE);
+ }
+
+ /**
* True if any app is interacting with the user.
*/
public boolean hasInteractingApp(List<String> packageNames) {
for (var packageName : packageNames) {
if (hasActiveAudio(packageName)
- || hasActiveNetwork(packageName)
|| isAppTopVisible(packageName)) {
return true;
}
}
- return false;
+ return hasActiveNetwork(packageNames);
}
/**
@@ -186,6 +207,11 @@
* True if there is an ongoing phone call.
*/
public boolean isInCall() {
+ // Simulate in-call during test
+ if (SystemProperties.getBoolean(
+ "debug.pm.gentle_update_test.is_in_call", false)) {
+ return true;
+ }
// TelecomManager doesn't handle the case where some apps don't implement ConnectionService.
// We check apps using voice communication to detect if the device is in call.
var tm = mContext.getSystemService(TelecomManager.class);
diff --git a/services/core/java/com/android/server/pm/GentleUpdateHelper.java b/services/core/java/com/android/server/pm/GentleUpdateHelper.java
index 9ad847d..ae80bab 100644
--- a/services/core/java/com/android/server/pm/GentleUpdateHelper.java
+++ b/services/core/java/com/android/server/pm/GentleUpdateHelper.java
@@ -39,7 +39,10 @@
import android.text.format.DateUtils;
import android.util.Slog;
+import com.android.internal.util.Preconditions;
+
import java.util.ArrayDeque;
+import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit;
@@ -101,6 +104,13 @@
public boolean isTimedOut() {
return SystemClock.elapsedRealtime() >= mFinishTime;
}
+ /**
+ * The remaining time before this pending check is timed out.
+ */
+ public long getRemainingTimeMillis() {
+ long timeout = mFinishTime - SystemClock.elapsedRealtime();
+ return Math.max(timeout, 0);
+ }
}
private final Context mContext;
@@ -108,6 +118,7 @@
private final AppStateHelper mAppStateHelper;
// Worker thread only
private final ArrayDeque<PendingInstallConstraintsCheck> mPendingChecks = new ArrayDeque<>();
+ private final ArrayList<CompletableFuture<Boolean>> mPendingIdleFutures = new ArrayList<>();
private boolean mHasPendingIdleJob;
GentleUpdateHelper(Context context, Looper looper, AppStateHelper appStateHelper) {
@@ -130,39 +141,41 @@
CompletableFuture<InstallConstraintsResult> checkInstallConstraints(
List<String> packageNames, InstallConstraints constraints,
long timeoutMillis) {
- var future = new CompletableFuture<InstallConstraintsResult>();
+ var resultFuture = new CompletableFuture<InstallConstraintsResult>();
mHandler.post(() -> {
- long clampedTimeoutMillis = timeoutMillis;
- if (constraints.isRequireDeviceIdle()) {
- // Device-idle-constraint is required. Clamp the timeout to ensure
- // timeout-check happens after device-idle-check.
- clampedTimeoutMillis = Math.max(timeoutMillis, PENDING_CHECK_MILLIS);
- }
-
var pendingCheck = new PendingInstallConstraintsCheck(
- packageNames, constraints, future, clampedTimeoutMillis);
- if (constraints.isRequireDeviceIdle()) {
- mPendingChecks.add(pendingCheck);
- // JobScheduler doesn't provide queries about whether the device is idle.
- // We schedule 2 tasks to determine device idle. If the idle job is executed
- // before the delayed runnable, we know the device is idle.
- // Note #processPendingCheck will be no-op for the task executed later.
- scheduleIdleJob();
- mHandler.postDelayed(() -> processPendingCheck(pendingCheck, false),
- PENDING_CHECK_MILLIS);
- } else if (!processPendingCheck(pendingCheck, false)) {
- // Not resolved. Schedule a job for re-check
- mPendingChecks.add(pendingCheck);
- scheduleIdleJob();
- }
-
- if (!future.isDone()) {
- // Ensure the pending check is resolved after timeout, no matter constraints
- // satisfied or not.
- mHandler.postDelayed(() -> processPendingCheck(pendingCheck, false),
- clampedTimeoutMillis);
- }
+ packageNames, constraints, resultFuture, timeoutMillis);
+ var deviceIdleFuture = constraints.isRequireDeviceIdle()
+ ? checkDeviceIdle() : CompletableFuture.completedFuture(false);
+ deviceIdleFuture.thenAccept(isIdle -> {
+ Preconditions.checkState(mHandler.getLooper().isCurrentThread());
+ if (!processPendingCheck(pendingCheck, isIdle)) {
+ // Not resolved. Schedule a job for re-check
+ mPendingChecks.add(pendingCheck);
+ scheduleIdleJob();
+ // Ensure the pending check is resolved after timeout, no matter constraints
+ // satisfied or not.
+ mHandler.postDelayed(() -> processPendingCheck(
+ pendingCheck, false), pendingCheck.getRemainingTimeMillis());
+ }
+ });
});
+ return resultFuture;
+ }
+
+ /**
+ * Checks if the device is idle or not.
+ * @return A future resolved to {@code true} if the device is idle, or {@code false} if not.
+ */
+ @WorkerThread
+ private CompletableFuture<Boolean> checkDeviceIdle() {
+ // JobScheduler doesn't provide queries about whether the device is idle.
+ // We schedule 2 tasks here and the task which resolves
+ // the future first will determine whether the device is idle or not.
+ var future = new CompletableFuture<Boolean>();
+ mPendingIdleFutures.add(future);
+ scheduleIdleJob();
+ mHandler.postDelayed(() -> future.complete(false), PENDING_CHECK_MILLIS);
return future;
}
@@ -194,6 +207,11 @@
private void runIdleJob() {
mHasPendingIdleJob = false;
processPendingChecksInIdle();
+
+ for (var f : mPendingIdleFutures) {
+ f.complete(true);
+ }
+ mPendingIdleFutures.clear();
}
@WorkerThread
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index edc6b4a..bf6bb22 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -1597,8 +1597,6 @@
ServiceManager.addService("package", iPackageManager);
final PackageManagerNative pmn = new PackageManagerNative(m);
ServiceManager.addService("package_native", pmn);
- LocalManagerRegistry.addManager(PackageManagerLocal.class,
- new PackageManagerLocalImpl(m));
return m;
}
@@ -1797,6 +1795,8 @@
// Expose private service for system components to use.
LocalServices.addService(PackageManagerInternal.class, new PackageManagerInternalImpl());
+ LocalManagerRegistry.addManager(PackageManagerLocal.class,
+ new PackageManagerLocalImpl(this));
LocalServices.addService(TestUtilityService.class, this);
mTestUtilityService = LocalServices.getService(TestUtilityService.class);
mUserManager = injector.getUserManagerService();
@@ -3461,6 +3461,11 @@
PackageManagerServiceUtils.enforceShellRestriction(mInjector.getUserManagerInternal(),
UserManager.DISALLOW_DEBUGGING_FEATURES, callingUid, sourceUserId);
+ if (!intentFilter.checkDataPathAndSchemeSpecificParts()) {
+ EventLog.writeEvent(0x534e4554, "246749936", callingUid);
+ throw new IllegalArgumentException("Invalid intent data paths or scheme specific parts"
+ + " in the filter.");
+ }
if (intentFilter.countActions() == 0) {
Slog.w(TAG, "Cannot set a crossProfile intent filter with no filter actions");
return;
diff --git a/services/core/java/com/android/server/pm/PreferredActivityHelper.java b/services/core/java/com/android/server/pm/PreferredActivityHelper.java
index e7727f0..6f8995c 100644
--- a/services/core/java/com/android/server/pm/PreferredActivityHelper.java
+++ b/services/core/java/com/android/server/pm/PreferredActivityHelper.java
@@ -37,6 +37,7 @@
import android.os.Process;
import android.os.UserHandle;
import android.text.TextUtils;
+import android.util.EventLog;
import android.util.Log;
import android.util.LogPrinter;
import android.util.PrintStreamPrinter;
@@ -387,6 +388,11 @@
throw new SecurityException(
"addPersistentPreferredActivity can only be run by the system");
}
+ if (!filter.checkDataPathAndSchemeSpecificParts()) {
+ EventLog.writeEvent(0x534e4554, "246749702", callingUid);
+ throw new IllegalArgumentException("Invalid intent data paths or scheme specific parts"
+ + " in the filter.");
+ }
if (filter.countActions() == 0) {
Slog.w(TAG, "Cannot set a preferred activity with no filter actions");
return;
diff --git a/services/core/java/com/android/server/pm/WatchedIntentFilter.java b/services/core/java/com/android/server/pm/WatchedIntentFilter.java
index 5d7a2a3..09cf094 100644
--- a/services/core/java/com/android/server/pm/WatchedIntentFilter.java
+++ b/services/core/java/com/android/server/pm/WatchedIntentFilter.java
@@ -671,6 +671,13 @@
}
/**
+ * @see IntentFilter#checkDataPathAndSchemeSpecificParts()
+ */
+ public boolean checkDataPathAndSchemeSpecificParts() {
+ return mFilter.checkDataPathAndSchemeSpecificParts();
+ }
+
+ /**
* @see IntentFilter#getHostsList()
*/
public ArrayList<String> getHostsList() {
diff --git a/services/core/java/com/android/server/power/PowerManagerService.java b/services/core/java/com/android/server/power/PowerManagerService.java
index 458bfeb..884d5d6 100644
--- a/services/core/java/com/android/server/power/PowerManagerService.java
+++ b/services/core/java/com/android/server/power/PowerManagerService.java
@@ -4251,8 +4251,8 @@
}
private boolean forceSuspendInternal(int uid) {
- try {
- synchronized (mLock) {
+ synchronized (mLock) {
+ try {
mForceSuspendActive = true;
// Place the system in an non-interactive state
for (int idx = 0; idx < mPowerGroups.size(); idx++) {
@@ -4262,16 +4262,14 @@
// Disable all the partial wake locks as well
updateWakeLockDisabledStatesLocked();
- }
- Slog.i(TAG, "Force-Suspending (uid " + uid + ")...");
- boolean success = mNativeWrapper.nativeForceSuspend();
- if (!success) {
- Slog.i(TAG, "Force-Suspending failed in native.");
- }
- return success;
- } finally {
- synchronized (mLock) {
+ Slog.i(TAG, "Force-Suspending (uid " + uid + ")...");
+ boolean success = mNativeWrapper.nativeForceSuspend();
+ if (!success) {
+ Slog.i(TAG, "Force-Suspending failed in native.");
+ }
+ return success;
+ } finally {
mForceSuspendActive = false;
// Re-enable wake locks once again.
updateWakeLockDisabledStatesLocked();
diff --git a/services/core/java/com/android/server/security/rkp/RemoteProvisioningRegistration.java b/services/core/java/com/android/server/security/rkp/RemoteProvisioningRegistration.java
new file mode 100644
index 0000000..868f34b
--- /dev/null
+++ b/services/core/java/com/android/server/security/rkp/RemoteProvisioningRegistration.java
@@ -0,0 +1,132 @@
+/*
+ * Copyright (C) 2022 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.security.rkp;
+
+import android.os.CancellationSignal;
+import android.os.OperationCanceledException;
+import android.os.OutcomeReceiver;
+import android.security.rkp.IGetKeyCallback;
+import android.security.rkp.IRegistration;
+import android.security.rkp.service.RegistrationProxy;
+import android.security.rkp.service.RemotelyProvisionedKey;
+import android.util.Log;
+
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.Executor;
+
+/**
+ * Implements android.security.rkp.IRegistration as a thin wrapper around the java code
+ * exported by com.android.rkp.
+ *
+ * @hide
+ */
+final class RemoteProvisioningRegistration extends IRegistration.Stub {
+ static final String TAG = RemoteProvisioningService.TAG;
+ private final ConcurrentHashMap<IGetKeyCallback, CancellationSignal> mOperations =
+ new ConcurrentHashMap<>();
+ private final RegistrationProxy mRegistration;
+ private final Executor mExecutor;
+
+ private class GetKeyReceiver implements OutcomeReceiver<RemotelyProvisionedKey, Exception> {
+ IGetKeyCallback mCallback;
+ GetKeyReceiver(IGetKeyCallback callback) {
+ mCallback = callback;
+ }
+
+ @Override
+ public void onResult(RemotelyProvisionedKey result) {
+ mOperations.remove(mCallback);
+ Log.i(TAG, "Successfully fetched key for client " + mCallback.hashCode());
+ android.security.rkp.RemotelyProvisionedKey parcelable =
+ new android.security.rkp.RemotelyProvisionedKey();
+ parcelable.keyBlob = result.getKeyBlob();
+ parcelable.encodedCertChain = result.getEncodedCertChain();
+ wrapCallback(() -> mCallback.onSuccess(parcelable));
+ }
+
+ @Override
+ public void onError(Exception e) {
+ mOperations.remove(mCallback);
+ if (e instanceof OperationCanceledException) {
+ Log.i(TAG, "Operation cancelled for client " + mCallback.hashCode());
+ wrapCallback(mCallback::onCancel);
+ } else {
+ Log.e(TAG, "Error fetching key for client " + mCallback.hashCode(), e);
+ wrapCallback(() -> mCallback.onError(e.getMessage()));
+ }
+ }
+ }
+
+ RemoteProvisioningRegistration(RegistrationProxy registration, Executor executor) {
+ mRegistration = registration;
+ mExecutor = executor;
+ }
+
+ @Override
+ public void getKey(int keyId, IGetKeyCallback callback) {
+ CancellationSignal cancellationSignal = new CancellationSignal();
+ if (mOperations.putIfAbsent(callback, cancellationSignal) != null) {
+ Log.e(TAG, "Client can only request one call at a time " + callback.hashCode());
+ throw new IllegalArgumentException(
+ "Callback is already associated with an existing operation: "
+ + callback.hashCode());
+ }
+
+ try {
+ Log.i(TAG, "Fetching key " + keyId + " for client " + callback.hashCode());
+ mRegistration.getKeyAsync(keyId, cancellationSignal, mExecutor,
+ new GetKeyReceiver(callback));
+ } catch (Exception e) {
+ Log.e(TAG, "getKeyAsync threw an exception for client " + callback.hashCode(), e);
+ mOperations.remove(callback);
+ wrapCallback(() -> callback.onError(e.getMessage()));
+ }
+ }
+
+ @Override
+ public void cancelGetKey(IGetKeyCallback callback) {
+ CancellationSignal cancellationSignal = mOperations.remove(callback);
+ if (cancellationSignal == null) {
+ throw new IllegalArgumentException(
+ "Invalid client in cancelGetKey: " + callback.hashCode());
+ }
+
+ Log.i(TAG, "Requesting cancellation for client " + callback.hashCode());
+ cancellationSignal.cancel();
+ }
+
+ @Override
+ public void storeUpgradedKey(byte[] oldKeyBlob, byte[] newKeyBlob) {
+ // TODO(b/262748535)
+ Log.e(TAG, "RegistrationBinder.storeUpgradedKey NOT YET IMPLEMENTED");
+ }
+
+ interface CallbackRunner {
+ void run() throws Exception;
+ }
+
+ private void wrapCallback(CallbackRunner callback) {
+ // Exceptions resulting from notifications to IGetKeyCallback objects can only be logged,
+ // since getKey execution is asynchronous, and there's no way for an exception to be
+ // properly handled up the stack.
+ try {
+ callback.run();
+ } catch (Exception e) {
+ Log.e(TAG, "Error invoking callback on client binder", e);
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/security/rkp/RemoteProvisioningService.java b/services/core/java/com/android/server/security/rkp/RemoteProvisioningService.java
index 65a4b38..cd1a968 100644
--- a/services/core/java/com/android/server/security/rkp/RemoteProvisioningService.java
+++ b/services/core/java/com/android/server/security/rkp/RemoteProvisioningService.java
@@ -20,9 +20,7 @@
import android.os.Binder;
import android.os.OutcomeReceiver;
import android.os.RemoteException;
-import android.security.rkp.IGetKeyCallback;
import android.security.rkp.IGetRegistrationCallback;
-import android.security.rkp.IRegistration;
import android.security.rkp.IRemoteProvisioning;
import android.security.rkp.service.RegistrationProxy;
import android.util.Log;
@@ -30,6 +28,7 @@
import com.android.server.SystemService;
import java.time.Duration;
+import java.util.concurrent.Executor;
/**
* Implements the remote provisioning system service. This service is backed by a mainline
@@ -43,6 +42,35 @@
private static final Duration CREATE_REGISTRATION_TIMEOUT = Duration.ofSeconds(10);
private final RemoteProvisioningImpl mBinderImpl = new RemoteProvisioningImpl();
+ private static class RegistrationReceiver implements
+ OutcomeReceiver<RegistrationProxy, Exception> {
+ private final Executor mExecutor;
+ private final IGetRegistrationCallback mCallback;
+
+ RegistrationReceiver(Executor executor, IGetRegistrationCallback callback) {
+ mExecutor = executor;
+ mCallback = callback;
+ }
+
+ @Override
+ public void onResult(RegistrationProxy registration) {
+ try {
+ mCallback.onSuccess(new RemoteProvisioningRegistration(registration, mExecutor));
+ } catch (RemoteException e) {
+ Log.e(TAG, "Error calling success callback " + mCallback.hashCode(), e);
+ }
+ }
+
+ @Override
+ public void onError(Exception error) {
+ try {
+ mCallback.onError(error.toString());
+ } catch (RemoteException e) {
+ Log.e(TAG, "Error calling error callback " + mCallback.hashCode(), e);
+ }
+ }
+ }
+
/** @hide */
public RemoteProvisioningService(Context context) {
super(context);
@@ -54,73 +82,20 @@
}
private final class RemoteProvisioningImpl extends IRemoteProvisioning.Stub {
-
- final class RegistrationBinder extends IRegistration.Stub {
- static final String TAG = RemoteProvisioningService.TAG;
- private final RegistrationProxy mRegistration;
-
- RegistrationBinder(RegistrationProxy registration) {
- mRegistration = registration;
- }
-
- @Override
- public void getKey(int keyId, IGetKeyCallback callback) {
- Log.e(TAG, "RegistrationBinder.getKey NOT YET IMPLEMENTED");
- }
-
- @Override
- public void cancelGetKey(IGetKeyCallback callback) {
- Log.e(TAG, "RegistrationBinder.cancelGetKey NOT YET IMPLEMENTED");
- }
-
- @Override
- public void storeUpgradedKey(byte[] oldKeyBlob, byte[] newKeyBlob) {
- Log.e(TAG, "RegistrationBinder.storeUpgradedKey NOT YET IMPLEMENTED");
- }
- }
-
@Override
public void getRegistration(String irpcName, IGetRegistrationCallback callback)
throws RemoteException {
final int callerUid = Binder.getCallingUidOrThrow();
final long callingIdentity = Binder.clearCallingIdentity();
+ final Executor executor = getContext().getMainExecutor();
try {
Log.i(TAG, "getRegistration(" + irpcName + ")");
- RegistrationProxy.createAsync(
- getContext(),
- callerUid,
- irpcName,
- CREATE_REGISTRATION_TIMEOUT,
- getContext().getMainExecutor(),
- new OutcomeReceiver<>() {
- @Override
- public void onResult(RegistrationProxy registration) {
- try {
- callback.onSuccess(new RegistrationBinder(registration));
- } catch (RemoteException e) {
- Log.e(TAG, "Error calling success callback", e);
- }
- }
-
- @Override
- public void onError(Exception error) {
- try {
- callback.onError(error.toString());
- } catch (RemoteException e) {
- Log.e(TAG, "Error calling error callback", e);
- }
- }
- });
+ RegistrationProxy.createAsync(getContext(), callerUid, irpcName,
+ CREATE_REGISTRATION_TIMEOUT, executor,
+ new RegistrationReceiver(executor, callback));
} finally {
Binder.restoreCallingIdentity(callingIdentity);
}
}
-
- @Override
- public void cancelGetRegistration(IGetRegistrationCallback callback)
- throws RemoteException {
- Log.i(TAG, "cancelGetRegistration()");
- callback.onError("cancelGetRegistration not yet implemented");
- }
}
}
diff --git a/services/core/java/com/android/server/speech/RemoteSpeechRecognitionService.java b/services/core/java/com/android/server/speech/RemoteSpeechRecognitionService.java
index f4b335e..411b2fa 100644
--- a/services/core/java/com/android/server/speech/RemoteSpeechRecognitionService.java
+++ b/services/core/java/com/android/server/speech/RemoteSpeechRecognitionService.java
@@ -19,46 +19,49 @@
import static com.android.internal.infra.AbstractRemoteService.PERMANENT_BOUND_TIMEOUT_MS;
import android.annotation.NonNull;
-import android.annotation.Nullable;
import android.content.AttributionSource;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
+import android.os.IBinder;
import android.os.RemoteException;
import android.speech.IRecognitionListener;
import android.speech.IRecognitionService;
import android.speech.IRecognitionSupportCallback;
import android.speech.RecognitionService;
import android.speech.SpeechRecognizer;
+import android.text.TextUtils;
import android.util.Log;
+import android.util.Pair;
import android.util.Slog;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.infra.ServiceConnector;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
final class RemoteSpeechRecognitionService extends ServiceConnector.Impl<IRecognitionService> {
private static final String TAG = RemoteSpeechRecognitionService.class.getSimpleName();
private static final boolean DEBUG = false;
+ /** Maximum number of clients connected to this object at the same time. */
+ private static final int MAX_CONCURRENT_CLIENTS = 100;
+
private final Object mLock = new Object();
private boolean mConnected = false;
- @Nullable
- private IRecognitionListener mListener;
-
- @Nullable
+ /** Map containing info about connected clients indexed by the their listeners. */
@GuardedBy("mLock")
- private DelegatingListener mDelegatingListener;
+ private final Map<IBinder, ClientState> mClients = new HashMap<>();
- // Makes sure we can block startListening() if session is still in progress.
+ /** List of pairs associating clients' binder tokens with corresponding listeners. */
@GuardedBy("mLock")
- private boolean mSessionInProgress = false;
-
- // Makes sure we call startProxyOp / finishProxyOp at right times and only once per session.
- @GuardedBy("mLock")
- private boolean mRecordingInProgress = false;
+ private final List<Pair<IBinder, IRecognitionListener>> mClientListeners = new ArrayList<>();
private final int mCallingUid;
private final ComponentName mComponentName;
@@ -78,7 +81,7 @@
mComponentName = serviceName;
if (DEBUG) {
- Slog.i(TAG, "Bound to recognition service at: " + serviceName.flattenToString());
+ Slog.i(TAG, "Bound to recognition service at: " + serviceName.flattenToString() + ".");
}
}
@@ -89,13 +92,14 @@
void startListening(Intent recognizerIntent, IRecognitionListener listener,
@NonNull AttributionSource attributionSource) {
if (DEBUG) {
- Slog.i(TAG, String.format("#startListening for package: %s, feature=%s, callingUid=%d",
+ Slog.i(TAG, TextUtils.formatSimple("#startListening for package: "
+ + "%s, feature=%s, callingUid=%d.",
attributionSource.getPackageName(), attributionSource.getAttributionTag(),
mCallingUid));
}
if (listener == null) {
- Log.w(TAG, "#startListening called with no preceding #setListening - ignoring");
+ Slog.w(TAG, "#startListening called with no preceding #setListening - ignoring.");
return;
}
@@ -105,29 +109,52 @@
}
synchronized (mLock) {
- if (mSessionInProgress) {
- Slog.i(TAG, "#startListening called while listening is in progress.");
- tryRespondWithError(listener, SpeechRecognizer.ERROR_RECOGNIZER_BUSY);
- return;
+ ClientState clientState = mClients.get(listener.asBinder());
+
+ if (clientState == null) {
+ if (mClients.size() >= MAX_CONCURRENT_CLIENTS) {
+ tryRespondWithError(listener, SpeechRecognizer.ERROR_RECOGNIZER_BUSY);
+ Log.i(TAG, "#startListening received "
+ + "when the recognizer's capacity is full - ignoring this call.");
+ return;
+ }
+
+ final ClientState newClientState = new ClientState();
+ newClientState.mDelegatingListener = new DelegatingListener(listener,
+ () -> {
+ // To be invoked in terminal calls on success.
+ if (DEBUG) {
+ Slog.i(TAG, "Recognition session completed successfully.");
+ }
+ synchronized (mLock) {
+ newClientState.mRecordingInProgress = false;
+ }
+ },
+ () -> {
+ // To be invoked in terminal calls on failure.
+ if (DEBUG) {
+ Slog.i(TAG, "Recognition session failed.");
+ }
+ removeClient(listener);
+ });
+
+ if (DEBUG) {
+ Log.d(TAG, "Added a new client to the map.");
+ }
+ mClients.put(listener.asBinder(), newClientState);
+ clientState = newClientState;
+ } else {
+ if (clientState.mRecordingInProgress) {
+ Slog.i(TAG, "#startListening called "
+ + "while listening is in progress for this caller.");
+ tryRespondWithError(listener, SpeechRecognizer.ERROR_CLIENT);
+ return;
+ }
+ clientState.mRecordingInProgress = true;
}
- mSessionInProgress = true;
- mRecordingInProgress = true;
-
- mListener = listener;
- mDelegatingListener = new DelegatingListener(listener, () -> {
- // To be invoked in terminal calls of the callback: results() or error()
- if (DEBUG) {
- Slog.i(TAG, "Recognition session complete");
- }
-
- synchronized (mLock) {
- resetStateLocked();
- }
- });
-
- // Eager local evaluation to avoid reading a different or null value at closure-run-time
- final DelegatingListener listenerToStart = this.mDelegatingListener;
+ // Eager local evaluation to avoid reading a different or null value at closure runtime.
+ final DelegatingListener listenerToStart = clientState.mDelegatingListener;
run(service ->
service.startListening(
recognizerIntent,
@@ -147,26 +174,22 @@
}
synchronized (mLock) {
- if (mListener == null) {
- Log.w(TAG, "#stopListening called with no preceding #startListening - ignoring");
+ ClientState clientState = mClients.get(listener.asBinder());
+
+ if (clientState == null) {
+ Slog.w(TAG, "#stopListening called with no preceding #startListening - ignoring.");
tryRespondWithError(listener, SpeechRecognizer.ERROR_CLIENT);
return;
}
-
- if (mListener.asBinder() != listener.asBinder()) {
- Log.w(TAG, "#stopListening called with an unexpected listener");
+ if (!clientState.mRecordingInProgress) {
tryRespondWithError(listener, SpeechRecognizer.ERROR_CLIENT);
+ Slog.i(TAG, "#stopListening called while listening isn't in progress - ignoring.");
return;
}
+ clientState.mRecordingInProgress = false;
- if (!mRecordingInProgress) {
- Slog.i(TAG, "#stopListening called while listening isn't in progress, ignoring.");
- return;
- }
- mRecordingInProgress = false;
-
- // Eager local evaluation to avoid reading a different or null value at closure-run-time
- final DelegatingListener listenerToStop = this.mDelegatingListener;
+ // Eager local evaluation to avoid reading a different or null value at closure runtime.
+ final DelegatingListener listenerToStop = clientState.mDelegatingListener;
run(service -> service.stopListening(listenerToStop));
}
}
@@ -181,33 +204,30 @@
}
synchronized (mLock) {
- if (mListener == null) {
+ ClientState clientState = mClients.get(listener.asBinder());
+
+ if (clientState == null) {
if (DEBUG) {
- Log.w(TAG, "#cancel called with no preceding #startListening - ignoring");
+ Slog.w(TAG, "#cancel called with no preceding #startListening - ignoring.");
}
return;
}
-
- if (mListener.asBinder() != listener.asBinder()) {
- Log.w(TAG, "#cancel called with an unexpected listener");
- tryRespondWithError(listener, SpeechRecognizer.ERROR_CLIENT);
- return;
- }
+ clientState.mRecordingInProgress = false;
// Temporary reference to allow for resetting the hard link mDelegatingListener to null.
- IRecognitionListener delegatingListener = mDelegatingListener;
-
+ final IRecognitionListener delegatingListener = clientState.mDelegatingListener;
run(service -> service.cancel(delegatingListener, isShutdown));
- mRecordingInProgress = false;
- mSessionInProgress = false;
-
- mDelegatingListener = null;
- mListener = null;
-
- // Schedule to unbind after cancel is delivered.
+ // If shutdown, remove the client info from the map. Unbind if that was the last client.
if (isShutdown) {
- run(service -> unbind());
+ removeClient(listener);
+
+ if (mClients.isEmpty()) {
+ if (DEBUG) {
+ Slog.d(TAG, "Unbinding from the recognition service.");
+ }
+ run(service -> unbind());
+ }
}
}
}
@@ -215,7 +235,6 @@
void checkRecognitionSupport(
Intent recognizerIntent,
IRecognitionSupportCallback callback) {
-
if (!mConnected) {
try {
callback.onError(SpeechRecognizer.ERROR_SERVER_DISCONNECTED);
@@ -236,18 +255,14 @@
run(service -> service.triggerModelDownload(recognizerIntent));
}
- void shutdown() {
+ void shutdown(IBinder clientToken) {
synchronized (mLock) {
- if (this.mListener == null) {
- if (DEBUG) {
- Slog.i(TAG, "Package died, but session wasn't initialized. "
- + "Not invoking #cancel");
+ for (Pair<IBinder, IRecognitionListener> clientListener : mClientListeners) {
+ if (clientListener.first == clientToken) {
+ cancel(clientListener.second, /* isShutdown */ true);
}
- return;
}
}
-
- cancel(mListener, true /* isShutdown */);
}
@Override // from ServiceConnector.Impl
@@ -265,15 +280,18 @@
synchronized (mLock) {
if (!connected) {
- if (mListener == null) {
+ if (mClients.isEmpty()) {
Slog.i(TAG, "Connection to speech recognition service lost, but no "
+ "#startListening has been invoked yet.");
return;
}
- tryRespondWithError(mListener, SpeechRecognizer.ERROR_SERVER_DISCONNECTED);
-
- resetStateLocked();
+ for (ClientState clientState : mClients.values()) {
+ tryRespondWithError(
+ clientState.mDelegatingListener.mRemoteListener,
+ SpeechRecognizer.ERROR_SERVER_DISCONNECTED);
+ removeClient(clientState.mDelegatingListener.mRemoteListener);
+ }
}
}
}
@@ -283,11 +301,18 @@
return PERMANENT_BOUND_TIMEOUT_MS;
}
- private void resetStateLocked() {
- mListener = null;
- mDelegatingListener = null;
- mSessionInProgress = false;
- mRecordingInProgress = false;
+ private void removeClient(IRecognitionListener listener) {
+ synchronized (mLock) {
+ ClientState clientState = mClients.remove(listener.asBinder());
+ if (clientState != null) {
+ if (DEBUG) {
+ Slog.d(TAG, "Removed a client from the map with listener = "
+ + listener.asBinder() + ".");
+ }
+ clientState.reset();
+ }
+ mClientListeners.removeIf(clientListener -> clientListener.second == listener);
+ }
}
private static void tryRespondWithError(IRecognitionListener listener, int errorCode) {
@@ -301,19 +326,35 @@
}
} catch (RemoteException e) {
Slog.w(TAG,
- String.format("Failed to respond with an error %d to the client", errorCode),
- e);
+ TextUtils.formatSimple("Failed to respond with an error %d to the client",
+ errorCode), e);
+ }
+ }
+
+ boolean hasActiveSessions() {
+ synchronized (mLock) {
+ return !mClients.isEmpty();
+ }
+ }
+
+ void associateClientWithActiveListener(IBinder clientToken, IRecognitionListener listener) {
+ synchronized (mLock) {
+ if (mClients.containsKey(listener.asBinder())) {
+ mClientListeners.add(new Pair<>(clientToken, listener));
+ }
}
}
private static class DelegatingListener extends IRecognitionListener.Stub {
-
private final IRecognitionListener mRemoteListener;
- private final Runnable mOnSessionComplete;
+ private final Runnable mOnSessionSuccess;
+ private final Runnable mOnSessionFailure;
- DelegatingListener(IRecognitionListener listener, Runnable onSessionComplete) {
+ DelegatingListener(IRecognitionListener listener,
+ Runnable onSessionSuccess, Runnable onSessionFailure) {
mRemoteListener = listener;
- mOnSessionComplete = onSessionComplete;
+ mOnSessionSuccess = onSessionSuccess;
+ mOnSessionFailure = onSessionFailure;
}
@Override
@@ -344,18 +385,18 @@
@Override
public void onError(int error) throws RemoteException {
if (DEBUG) {
- Slog.i(TAG, String.format("Error %d during recognition session", error));
+ Slog.i(TAG, TextUtils.formatSimple("Error %d during recognition session.", error));
}
- mOnSessionComplete.run();
+ mOnSessionFailure.run();
mRemoteListener.onError(error);
}
@Override
public void onResults(Bundle results) throws RemoteException {
if (DEBUG) {
- Slog.i(TAG, "#onResults invoked for a recognition session");
+ Slog.i(TAG, "#onResults invoked for a recognition session.");
}
- mOnSessionComplete.run();
+ mOnSessionSuccess.run();
mRemoteListener.onResults(results);
}
@@ -372,9 +413,9 @@
@Override
public void onEndOfSegmentedSession() throws RemoteException {
if (DEBUG) {
- Slog.i(TAG, "#onEndOfSegmentedSession invoked for a recognition session");
+ Slog.i(TAG, "#onEndOfSegmentedSession invoked for a recognition session.");
}
- mOnSessionComplete.run();
+ mOnSessionSuccess.run();
mRemoteListener.onEndOfSegmentedSession();
}
@@ -383,4 +424,35 @@
mRemoteListener.onEvent(eventType, params);
}
}
+
+ /**
+ * Data class holding info about a connected client:
+ * <ul>
+ * <li> {@link ClientState#mDelegatingListener}
+ * - object holding callbacks to be invoked after the session is complete;
+ * <li> {@link ClientState#mRecordingInProgress}
+ * - flag denoting if the client is currently recording.
+ */
+ static class ClientState {
+ DelegatingListener mDelegatingListener;
+ boolean mRecordingInProgress;
+
+ ClientState(DelegatingListener delegatingListener, boolean recordingInProgress) {
+ mDelegatingListener = delegatingListener;
+ mRecordingInProgress = recordingInProgress;
+ }
+
+ ClientState(DelegatingListener delegatingListener) {
+ this(delegatingListener, true);
+ }
+
+ ClientState() {
+ this(null, true);
+ }
+
+ void reset() {
+ mDelegatingListener = null;
+ mRecordingInProgress = false;
+ }
+ }
}
diff --git a/services/core/java/com/android/server/speech/SpeechRecognitionManagerServiceImpl.java b/services/core/java/com/android/server/speech/SpeechRecognitionManagerServiceImpl.java
index d5b73b7..e245c08 100644
--- a/services/core/java/com/android/server/speech/SpeechRecognitionManagerServiceImpl.java
+++ b/services/core/java/com/android/server/speech/SpeechRecognitionManagerServiceImpl.java
@@ -51,7 +51,7 @@
final class SpeechRecognitionManagerServiceImpl extends
AbstractPerUserSystemService<SpeechRecognitionManagerServiceImpl,
- SpeechRecognitionManagerService> {
+ SpeechRecognitionManagerService> {
private static final String TAG = SpeechRecognitionManagerServiceImpl.class.getSimpleName();
private static final int MAX_CONCURRENT_CONNECTIONS_BY_CLIENT = 10;
@@ -127,13 +127,14 @@
}
IBinder.DeathRecipient deathRecipient =
- () -> handleClientDeath(creatorCallingUid, service, true /* invoke #cancel */);
+ () -> handleClientDeath(
+ clientToken, creatorCallingUid, service, true /* invoke #cancel */);
try {
clientToken.linkToDeath(deathRecipient, 0);
} catch (RemoteException e) {
// RemoteException == binder already died, schedule disconnect anyway.
- handleClientDeath(creatorCallingUid, service, true /* invoke #cancel */);
+ handleClientDeath(clientToken, creatorCallingUid, service, true /* invoke #cancel */);
return;
}
@@ -146,7 +147,7 @@
Intent recognizerIntent,
IRecognitionListener listener,
@NonNull AttributionSource attributionSource)
- throws RemoteException {
+ throws RemoteException {
attributionSource.enforceCallingUid();
if (!attributionSource.isTrusted(mMaster.getContext())) {
attributionSource = mMaster.getContext()
@@ -154,6 +155,7 @@
.registerAttributionSource(attributionSource);
}
service.startListening(recognizerIntent, listener, attributionSource);
+ service.associateClientWithActiveListener(clientToken, listener);
}
@Override
@@ -166,11 +168,11 @@
public void cancel(
IRecognitionListener listener,
boolean isShutdown) throws RemoteException {
-
service.cancel(listener, isShutdown);
if (isShutdown) {
handleClientDeath(
+ clientToken,
creatorCallingUid,
service,
false /* invoke #cancel */);
@@ -201,12 +203,16 @@
}
private void handleClientDeath(
- int callingUid,
+ IBinder clientToken, int callingUid,
RemoteSpeechRecognitionService service, boolean invokeCancel) {
if (invokeCancel) {
- service.shutdown();
+ service.shutdown(clientToken);
}
- removeService(callingUid, service);
+ synchronized (mLock) {
+ if (!service.hasActiveSessions()) {
+ removeService(callingUid, service);
+ }
+ }
}
@GuardedBy("mLock")
@@ -245,7 +251,6 @@
service.getServiceComponentName().equals(serviceComponent))
.findFirst();
if (existingService.isPresent()) {
-
if (mMaster.debug) {
Slog.i(TAG, "Reused existing connection to " + serviceComponent);
}
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
index e475eb6..937f2f1 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
@@ -4841,6 +4841,7 @@
// If there is no resumed activity, it will choose the pausing or focused activity.
: mRootWindowContainer.getTopResumedActivity();
mTopApp = top != null ? top.app : null;
+ if (mTopApp == mPreviousProcess) mPreviousProcess = null;
}
/**
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index ffea07c..9f56a1e 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -132,11 +132,15 @@
import static com.android.server.wm.DisplayContentProto.ROOT_DISPLAY_AREA;
import static com.android.server.wm.DisplayContentProto.SCREEN_ROTATION_ANIMATION;
import static com.android.server.wm.DisplayContentProto.SLEEP_TOKENS;
+import static com.android.server.wm.EventLogTags.IMF_REMOVE_IME_SCREENSHOT;
+import static com.android.server.wm.EventLogTags.IMF_SHOW_IME_SCREENSHOT;
+import static com.android.server.wm.EventLogTags.IMF_UPDATE_IME_PARENT;
import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_WINDOW_ANIMATION;
import static com.android.server.wm.WindowContainer.AnimationFlags.PARENTS;
import static com.android.server.wm.WindowContainer.AnimationFlags.TRANSITION;
import static com.android.server.wm.WindowContainerChildProto.DISPLAY_CONTENT;
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_DISPLAY;
+import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_IME_VISIBILITY;
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_INPUT_METHOD;
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_LAYOUT;
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_LAYOUT_REPEATS;
@@ -196,6 +200,7 @@
import android.util.ArraySet;
import android.util.DisplayMetrics;
import android.util.DisplayUtils;
+import android.util.EventLog;
import android.util.IntArray;
import android.util.Pair;
import android.util.RotationUtils;
@@ -4276,6 +4281,7 @@
private WindowState mImeTarget;
private SurfaceControl.Builder mSurfaceBuilder;
private SurfaceControl mImeSurface;
+ private Point mImeSurfacePosition;
ImeScreenshot(SurfaceControl.Builder surfaceBuilder, @NonNull WindowState imeTarget) {
mSurfaceBuilder = surfaceBuilder;
@@ -4326,6 +4332,7 @@
mImeTarget.mAttrs.surfaceInsets.top);
t.setPosition(imeSurface, surfacePosition.x, surfacePosition.y);
}
+ mImeSurfacePosition = surfacePosition;
ProtoLog.i(WM_DEBUG_IME, "Set IME snapshot position: (%d, %d)", surfacePosition.x,
surfacePosition.y);
return imeSurface;
@@ -4337,6 +4344,9 @@
t.remove(mImeSurface);
mImeSurface = null;
}
+ if (DEBUG_IME_VISIBILITY) {
+ EventLog.writeEvent(IMF_REMOVE_IME_SCREENSHOT, mImeTarget.toString());
+ }
}
void attachAndShow(Transaction t) {
@@ -4366,6 +4376,10 @@
ProtoLog.i(WM_DEBUG_IME, "show IME snapshot, ime target=%s, callers=%s",
mImeTarget, Debug.getCallers(6));
t.show(mImeSurface);
+ if (DEBUG_IME_VISIBILITY) {
+ EventLog.writeEvent(IMF_SHOW_IME_SCREENSHOT, mImeTarget.toString(),
+ dc.mInputMethodWindow.mTransitFlags, mImeSurfacePosition.toString());
+ }
} else if (!isValidSnapshot) {
removeImeSurface(t);
}
@@ -4511,6 +4525,9 @@
if (newParent != null && newParent != mInputMethodSurfaceParent) {
mInputMethodSurfaceParent = newParent;
getSyncTransaction().reparent(mImeWindowsContainer.mSurfaceControl, newParent);
+ if (DEBUG_IME_VISIBILITY) {
+ EventLog.writeEvent(IMF_UPDATE_IME_PARENT, newParent.toString());
+ }
// When surface parent is removed, the relative layer will also be removed. We need to
// do a force update to make sure there is a layer set for the new parent.
assignRelativeLayerForIme(getSyncTransaction(), true /* forceUpdate */);
diff --git a/services/core/java/com/android/server/wm/EventLogTags.logtags b/services/core/java/com/android/server/wm/EventLogTags.logtags
index d94bf4b..b36f835 100644
--- a/services/core/java/com/android/server/wm/EventLogTags.logtags
+++ b/services/core/java/com/android/server/wm/EventLogTags.logtags
@@ -62,5 +62,13 @@
# bootanim finished:
31007 wm_boot_animation_done (time|2|3)
+
+# IME surface parent is updated.
+32003 imf_update_ime_parent (surface name|3)
+# IME snapshot is shown.
+32004 imf_show_ime_screenshot (target window|3),(transition|1),(surface position|3)
+# IME snapshot is hidden.
+32005 imf_remove_ime_screenshot (target window|3)
+
# Request surface flinger to show / hide the wallpaper surface.
33001 wm_wallpaper_surface (Display Id|1|5),(Visible|1),(Target|3)
diff --git a/services/core/java/com/android/server/wm/Session.java b/services/core/java/com/android/server/wm/Session.java
index b482181..bf3edcf 100644
--- a/services/core/java/com/android/server/wm/Session.java
+++ b/services/core/java/com/android/server/wm/Session.java
@@ -856,23 +856,19 @@
@Override
public void grantInputChannel(int displayId, SurfaceControl surface,
IWindow window, IBinder hostInputToken, int flags, int privateFlags, int type,
- IBinder focusGrantToken, String inputHandleName, InputChannel outInputChannel) {
+ IBinder windowToken, IBinder focusGrantToken, String inputHandleName,
+ InputChannel outInputChannel) {
if (hostInputToken == null && !mCanAddInternalSystemWindow) {
// Callers without INTERNAL_SYSTEM_WINDOW permission cannot grant input channel to
// embedded windows without providing a host window input token
throw new SecurityException("Requires INTERNAL_SYSTEM_WINDOW permission");
}
- if (!mCanAddInternalSystemWindow && type != 0) {
- Slog.w(TAG_WM, "Requires INTERNAL_SYSTEM_WINDOW permission if assign type to"
- + " input");
- }
-
final long identity = Binder.clearCallingIdentity();
try {
mService.grantInputChannel(this, mUid, mPid, displayId, surface, window, hostInputToken,
flags, mCanAddInternalSystemWindow ? privateFlags : 0,
- mCanAddInternalSystemWindow ? type : 0, focusGrantToken, inputHandleName,
+ type, windowToken, focusGrantToken, inputHandleName,
outInputChannel);
} finally {
Binder.restoreCallingIdentity(identity);
diff --git a/services/core/java/com/android/server/wm/Transition.java b/services/core/java/com/android/server/wm/Transition.java
index 0c20d03..a1ddd58 100644
--- a/services/core/java/com/android/server/wm/Transition.java
+++ b/services/core/java/com/android/server/wm/Transition.java
@@ -171,7 +171,7 @@
final ArraySet<WindowContainer> mParticipants = new ArraySet<>();
/** The final animation targets derived from participants after promotion. */
- private ArrayList<WindowContainer> mTargets;
+ private ArrayList<ChangeInfo> mTargets;
/** The displays that this transition is running on. */
private final ArrayList<DisplayContent> mTargetDisplays = new ArrayList<>();
@@ -625,7 +625,7 @@
// usually only size 1
final ArraySet<DisplayContent> displays = new ArraySet<>();
for (int i = mTargets.size() - 1; i >= 0; --i) {
- final WindowContainer target = mTargets.get(i);
+ final WindowContainer target = mTargets.get(i).mContainer;
if (target.getParent() != null) {
final SurfaceControl targetLeash = getLeashSurface(target, null /* t */);
final SurfaceControl origParent = getOrigParentSurface(target);
@@ -858,7 +858,7 @@
for (int i = 0; i < mTargetDisplays.size(); ++i) {
final DisplayContent dc = mTargetDisplays.get(i);
final AsyncRotationController asyncRotationController = dc.getAsyncRotationController();
- if (asyncRotationController != null && mTargets.contains(dc)) {
+ if (asyncRotationController != null && containsChangeFor(dc, mTargets)) {
asyncRotationController.onTransitionFinished();
}
if (mTransientLaunches != null) {
@@ -925,6 +925,14 @@
change.mFlags |= ChangeInfo.FLAG_CHANGE_NO_ANIMATION;
}
+ @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
+ static boolean containsChangeFor(WindowContainer wc, ArrayList<ChangeInfo> list) {
+ for (int i = list.size() - 1; i >= 0; --i) {
+ if (list.get(i).mContainer == wc) return true;
+ }
+ return false;
+ }
+
@Override
public void onTransactionReady(int syncId, SurfaceControl.Transaction transaction) {
if (syncId != mSyncId) {
@@ -962,8 +970,7 @@
.containsBackAnimationTargets(this);
// Resolve the animating targets from the participants
mTargets = calculateTargets(mParticipants, mChanges);
- final TransitionInfo info = calculateTransitionInfo(mType, mFlags, mTargets, mChanges,
- transaction);
+ final TransitionInfo info = calculateTransitionInfo(mType, mFlags, mTargets, transaction);
if (markBackAnimated) {
mController.mAtm.mBackNavigationController.clearBackAnimations(mStartTransaction);
}
@@ -972,7 +979,7 @@
if (mOverrideOptions.getType() == ANIM_OPEN_CROSS_PROFILE_APPS) {
for (int i = 0; i < mTargets.size(); ++i) {
final TransitionInfo.Change c = info.getChanges().get(i);
- final ActivityRecord ar = mTargets.get(i).asActivityRecord();
+ final ActivityRecord ar = mTargets.get(i).mContainer.asActivityRecord();
if (ar == null || c.getMode() != TRANSIT_OPEN) continue;
int flags = c.getFlags();
flags |= ar.mUserId == ar.mWmService.mCurrentUserId
@@ -1016,7 +1023,7 @@
// already been reset by the original hiding-transition's finishTransaction (we can't
// show in the finishTransaction because by then the activity doesn't hide until
// surface placement).
- for (WindowContainer p = ar.getParent(); p != null && !mTargets.contains(p);
+ for (WindowContainer p = ar.getParent(); p != null && !containsChangeFor(p, mTargets);
p = p.getParent()) {
if (p.getSurfaceControl() != null) {
transaction.show(p.getSurfaceControl());
@@ -1052,7 +1059,7 @@
// This is non-null only if display has changes. It handles the visible windows that don't
// need to be participated in the transition.
final AsyncRotationController controller = dc.getAsyncRotationController();
- if (controller != null && mTargets.contains(dc)) {
+ if (controller != null && containsChangeFor(dc, mTargets)) {
controller.setupStartTransaction(transaction);
}
buildFinishTransaction(mFinishTransaction, info.getRootLeash());
@@ -1225,7 +1232,7 @@
// Search for the home task. If it is supposed to be visible, then the navbar is not at
// the bottom of the screen, so we need to animate it.
for (int i = 0; i < mTargets.size(); ++i) {
- final Task task = mTargets.get(i).asTask();
+ final Task task = mTargets.get(i).mContainer.asTask();
if (task == null || !task.isActivityTypeHomeOrRecents()) continue;
animate = task.isVisibleRequested();
break;
@@ -1350,12 +1357,13 @@
*
* @return {@code true} if transition in target can be promoted to its parent.
*/
- private static boolean canPromote(WindowContainer<?> target, Targets targets,
+ private static boolean canPromote(ChangeInfo targetChange, Targets targets,
ArrayMap<WindowContainer, ChangeInfo> changes) {
+ final WindowContainer<?> target = targetChange.mContainer;
final WindowContainer<?> parent = target.getParent();
final ChangeInfo parentChange = changes.get(parent);
if (!parent.canCreateRemoteAnimationTarget()
- || parentChange == null || !parentChange.hasChanged(parent)) {
+ || parentChange == null || !parentChange.hasChanged()) {
ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, " SKIP: %s",
"parent can't be target " + parent);
return false;
@@ -1365,21 +1373,20 @@
return false;
}
- final ChangeInfo change = changes.get(target);
- if (change.mStartParent != null && target.getParent() != change.mStartParent) {
+ if (targetChange.mStartParent != null && target.getParent() != targetChange.mStartParent) {
// When a window is reparented, the state change won't fit into any of the parents.
// Don't promote such change so that we can animate the reparent if needed.
return false;
}
- final @TransitionInfo.TransitionMode int mode = change.getTransitMode(target);
+ final @TransitionInfo.TransitionMode int mode = targetChange.getTransitMode(target);
for (int i = parent.getChildCount() - 1; i >= 0; --i) {
final WindowContainer<?> sibling = parent.getChildAt(i);
if (target == sibling) continue;
ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, " check sibling %s",
sibling);
final ChangeInfo siblingChange = changes.get(sibling);
- if (siblingChange == null || !targets.wasParticipated(sibling)) {
+ if (siblingChange == null || !targets.wasParticipated(siblingChange)) {
if (sibling.isVisibleRequested()) {
// Sibling is visible but not animating, so no promote.
ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS,
@@ -1424,7 +1431,8 @@
WindowContainer<?> lastNonPromotableParent = null;
// Go through from the deepest target.
for (int i = targets.mArray.size() - 1; i >= 0; --i) {
- final WindowContainer<?> target = targets.mArray.valueAt(i);
+ final ChangeInfo targetChange = targets.mArray.valueAt(i);
+ final WindowContainer<?> target = targetChange.mContainer;
ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, " checking %s", target);
final WindowContainer<?> parent = target.getParent();
if (parent == lastNonPromotableParent) {
@@ -1432,7 +1440,7 @@
" SKIP: its sibling was rejected");
continue;
}
- if (!canPromote(target, targets, changes)) {
+ if (!canPromote(targetChange, targets, changes)) {
lastNonPromotableParent = parent;
continue;
}
@@ -1442,19 +1450,20 @@
} else {
ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS,
" remove from targets %s", target);
- targets.remove(i, target);
+ targets.remove(i);
}
- if (targets.mArray.indexOfValue(parent) < 0) {
+ final ChangeInfo parentChange = changes.get(parent);
+ if (targets.mArray.indexOfValue(parentChange) < 0) {
ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS,
" CAN PROMOTE: promoting to parent %s", parent);
// The parent has lower depth, so it will be checked in the later iteration.
i++;
- targets.add(parent);
+ targets.add(parentChange);
}
- if ((changes.get(target).mFlags & ChangeInfo.FLAG_CHANGE_NO_ANIMATION) != 0) {
- changes.get(parent).mFlags |= ChangeInfo.FLAG_CHANGE_NO_ANIMATION;
+ if ((targetChange.mFlags & ChangeInfo.FLAG_CHANGE_NO_ANIMATION) != 0) {
+ parentChange.mFlags |= ChangeInfo.FLAG_CHANGE_NO_ANIMATION;
} else {
- changes.get(parent).mFlags |= ChangeInfo.FLAG_CHANGE_YES_ANIMATION;
+ parentChange.mFlags |= ChangeInfo.FLAG_CHANGE_YES_ANIMATION;
}
}
}
@@ -1465,7 +1474,7 @@
*/
@VisibleForTesting
@NonNull
- static ArrayList<WindowContainer> calculateTargets(ArraySet<WindowContainer> participants,
+ static ArrayList<ChangeInfo> calculateTargets(ArraySet<WindowContainer> participants,
ArrayMap<WindowContainer, ChangeInfo> changes) {
ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS,
"Start calculating TransitionInfo based on participants: %s", participants);
@@ -1485,12 +1494,12 @@
final ChangeInfo changeInfo = changes.get(wc);
// Reject no-ops
- if (!changeInfo.hasChanged(wc)) {
+ if (!changeInfo.hasChanged()) {
ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS,
" Rejecting as no-op: %s", wc);
continue;
}
- targets.add(wc);
+ targets.add(changeInfo);
}
ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, " Initial targets: %s",
targets.mArray);
@@ -1499,7 +1508,7 @@
// Establish the relationship between the targets and their top changes.
populateParentChanges(targets, changes);
- final ArrayList<WindowContainer> targetList = targets.getListSortedByZ();
+ final ArrayList<ChangeInfo> targetList = targets.getListSortedByZ();
ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, " Final targets: %s", targetList);
return targetList;
}
@@ -1507,14 +1516,15 @@
/** Populates parent to the change info and collects intermediate targets. */
private static void populateParentChanges(Targets targets,
ArrayMap<WindowContainer, ChangeInfo> changes) {
- final ArrayList<WindowContainer<?>> intermediates = new ArrayList<>();
+ final ArrayList<ChangeInfo> intermediates = new ArrayList<>();
// Make a copy to iterate because the original array may be modified.
- final ArrayList<WindowContainer<?>> targetList = new ArrayList<>(targets.mArray.size());
+ final ArrayList<ChangeInfo> targetList = new ArrayList<>(targets.mArray.size());
for (int i = targets.mArray.size() - 1; i >= 0; --i) {
targetList.add(targets.mArray.valueAt(i));
}
for (int i = targetList.size() - 1; i >= 0; --i) {
- final WindowContainer<?> wc = targetList.get(i);
+ final ChangeInfo targetChange = targetList.get(i);
+ final WindowContainer wc = targetChange.mContainer;
// Wallpaper must belong to the top (regardless of how nested it is in DisplayAreas).
final boolean skipIntermediateReports = isWallpaper(wc);
intermediates.clear();
@@ -1523,34 +1533,34 @@
for (WindowContainer<?> p = getAnimatableParent(wc); p != null;
p = getAnimatableParent(p)) {
final ChangeInfo parentChange = changes.get(p);
- if (parentChange == null || !parentChange.hasChanged(p)) break;
+ if (parentChange == null || !parentChange.hasChanged()) break;
if (p.mRemoteToken == null) {
// Intermediate parents must be those that has window to be managed by Shell.
continue;
}
if (parentChange.mEndParent != null && !skipIntermediateReports) {
- changes.get(wc).mEndParent = p;
+ targetChange.mEndParent = p;
// The chain above the parent was processed.
break;
}
- if (targetList.contains(p)) {
+ if (targetList.contains(parentChange)) {
if (skipIntermediateReports) {
- changes.get(wc).mEndParent = p;
+ targetChange.mEndParent = p;
} else {
- intermediates.add(p);
+ intermediates.add(parentChange);
}
foundParentInTargets = true;
break;
} else if (reportIfNotTop(p) && !skipIntermediateReports) {
- intermediates.add(p);
+ intermediates.add(parentChange);
}
}
if (!foundParentInTargets || intermediates.isEmpty()) continue;
// Add any always-report parents along the way.
- changes.get(wc).mEndParent = intermediates.get(0);
+ targetChange.mEndParent = intermediates.get(0).mContainer;
for (int j = 0; j < intermediates.size() - 1; j++) {
- final WindowContainer<?> intermediate = intermediates.get(j);
- changes.get(intermediate).mEndParent = intermediates.get(j + 1);
+ final ChangeInfo intermediate = intermediates.get(j);
+ intermediate.mEndParent = intermediates.get(j + 1).mContainer;
targets.add(intermediate);
}
}
@@ -1611,14 +1621,13 @@
@VisibleForTesting
@NonNull
static TransitionInfo calculateTransitionInfo(@TransitionType int type, int flags,
- ArrayList<WindowContainer> sortedTargets,
- ArrayMap<WindowContainer, ChangeInfo> changes,
+ ArrayList<ChangeInfo> sortedTargets,
@Nullable SurfaceControl.Transaction startT) {
final TransitionInfo out = new TransitionInfo(type, flags);
WindowContainer<?> topApp = null;
for (int i = 0; i < sortedTargets.size(); i++) {
- final WindowContainer<?> wc = sortedTargets.get(i);
+ final WindowContainer<?> wc = sortedTargets.get(i).mContainer;
if (!isWallpaper(wc)) {
topApp = wc;
break;
@@ -1629,7 +1638,7 @@
return out;
}
- WindowContainer<?> ancestor = findCommonAncestor(sortedTargets, changes, topApp);
+ WindowContainer<?> ancestor = findCommonAncestor(sortedTargets, topApp);
// Make leash based on highest (z-order) direct child of ancestor with a participant.
// TODO(b/261418859): Handle the case when the target contains window containers which
@@ -1647,8 +1656,8 @@
// Convert all the resolved ChangeInfos into TransactionInfo.Change objects in order.
final int count = sortedTargets.size();
for (int i = 0; i < count; ++i) {
- final WindowContainer target = sortedTargets.get(i);
- final ChangeInfo info = changes.get(target);
+ final ChangeInfo info = sortedTargets.get(i);
+ final WindowContainer target = info.mContainer;
final TransitionInfo.Change change = new TransitionInfo.Change(
target.mRemoteToken != null ? target.mRemoteToken.toWindowContainerToken()
: null, getLeashSurface(target, startT));
@@ -1758,14 +1767,14 @@
*/
@NonNull
private static WindowContainer<?> findCommonAncestor(
- @NonNull ArrayList<WindowContainer> targets,
- @NonNull ArrayMap<WindowContainer, ChangeInfo> changes,
+ @NonNull ArrayList<ChangeInfo> targets,
@NonNull WindowContainer<?> topApp) {
WindowContainer<?> ancestor = topApp.getParent();
// Go up ancestor parent chain until all targets are descendants. Ancestor should never be
// null because all targets are attached.
for (int i = targets.size() - 1; i >= 0; i--) {
- final WindowContainer wc = targets.get(i);
+ final ChangeInfo change = targets.get(i);
+ final WindowContainer wc = change.mContainer;
if (isWallpaper(wc)) {
// Skip the non-app window.
continue;
@@ -1777,7 +1786,6 @@
// Make sure the previous parent is also a descendant to make sure the animation won't
// be covered by other windows below the previous parent. For example, when reparenting
// an activity from PiP Task to split screen Task.
- final ChangeInfo change = changes.get(wc);
final WindowContainer prevParent = change.mCommonAncestor;
if (prevParent == null || !prevParent.isAttached()) {
continue;
@@ -1790,11 +1798,13 @@
}
private static WindowManager.LayoutParams getLayoutParamsForAnimationsStyle(int type,
- ArrayList<WindowContainer> sortedTargets) {
+ ArrayList<ChangeInfo> sortedTargets) {
// Find the layout params of the top-most application window that is part of the
// transition, which is what will control the animation theme.
final ArraySet<Integer> activityTypes = new ArraySet<>();
- for (WindowContainer target : sortedTargets) {
+ final int targetCount = sortedTargets.size();
+ for (int i = 0; i < targetCount; ++i) {
+ final WindowContainer target = sortedTargets.get(i).mContainer;
if (target.asActivityRecord() != null) {
activityTypes.add(target.getActivityType());
} else if (target.asWindowToken() == null && target.asWindowState() == null) {
@@ -1818,7 +1828,7 @@
}
private static ActivityRecord findAnimLayoutParamsActivityRecord(
- List<WindowContainer> sortedTargets,
+ List<ChangeInfo> sortedTargets,
@TransitionType int transit, ArraySet<Integer> activityTypes) {
// Remote animations always win, but fullscreen windows override non-fullscreen windows.
ActivityRecord result = lookForTopWindowWithFilter(sortedTargets,
@@ -1835,9 +1845,11 @@
return lookForTopWindowWithFilter(sortedTargets, w -> w.findMainWindow() != null);
}
- private static ActivityRecord lookForTopWindowWithFilter(List<WindowContainer> sortedTargets,
+ private static ActivityRecord lookForTopWindowWithFilter(List<ChangeInfo> sortedTargets,
Predicate<ActivityRecord> filter) {
- for (WindowContainer target : sortedTargets) {
+ final int count = sortedTargets.size();
+ for (int i = 0; i < count; ++i) {
+ final WindowContainer target = sortedTargets.get(i).mContainer;
final ActivityRecord activityRecord = target.asTaskFragment() != null
? target.asTaskFragment().getTopNonFinishingActivity()
: target.asActivityRecord();
@@ -1871,7 +1883,7 @@
for (int i = mParticipants.size() - 1; i >= 0; --i) {
final WindowContainer<?> wc = mParticipants.valueAt(i);
final DisplayContent dc = wc.asDisplayContent();
- if (dc == null || !mChanges.get(dc).hasChanged(dc)) continue;
+ if (dc == null || !mChanges.get(dc).hasChanged()) continue;
dc.sendNewConfiguration();
changed = true;
}
@@ -1913,6 +1925,7 @@
@Retention(RetentionPolicy.SOURCE)
@interface Flag {}
+ @NonNull final WindowContainer mContainer;
/**
* "Parent" that is also included in the transition. When populating the parent changes, we
* may skip the intermediate parents, so this may not be the actual parent in the hierarchy.
@@ -1945,6 +1958,7 @@
float mSnapshotLuma;
ChangeInfo(@NonNull WindowContainer origState) {
+ mContainer = origState;
mVisible = origState.isVisibleRequested();
mWindowingMode = origState.getWindowingMode();
mAbsoluteBounds.set(origState.getBounds());
@@ -1954,13 +1968,19 @@
}
@VisibleForTesting
- ChangeInfo(boolean visible, boolean existChange) {
+ ChangeInfo(@NonNull WindowContainer container, boolean visible, boolean existChange) {
+ mContainer = container;
mVisible = visible;
mExistenceChanged = existChange;
mShowWallpaper = false;
}
- boolean hasChanged(@NonNull WindowContainer newState) {
+ @Override
+ public String toString() {
+ return mContainer.toString();
+ }
+
+ boolean hasChanged() {
// the task including transient launch must promote to root task
if ((mFlags & ChangeInfo.FLAG_TRANSIENT_LAUNCH) != 0
|| (mFlags & ChangeInfo.FLAG_ABOVE_TRANSIENT_LAUNCH) != 0) {
@@ -1968,15 +1988,15 @@
}
// If it's invisible and hasn't changed visibility, always return false since even if
// something changed, it wouldn't be a visible change.
- final boolean currVisible = newState.isVisibleRequested();
+ final boolean currVisible = mContainer.isVisibleRequested();
if (currVisible == mVisible && !mVisible) return false;
return currVisible != mVisible
|| mKnownConfigChanges != 0
// if mWindowingMode is 0, this container wasn't attached at collect time, so
// assume no change in windowing-mode.
- || (mWindowingMode != 0 && newState.getWindowingMode() != mWindowingMode)
- || !newState.getBounds().equals(mAbsoluteBounds)
- || mRotation != newState.getWindowConfiguration().getRotation();
+ || (mWindowingMode != 0 && mContainer.getWindowingMode() != mWindowingMode)
+ || !mContainer.getBounds().equals(mAbsoluteBounds)
+ || mRotation != mContainer.getWindowConfiguration().getRotation();
}
@TransitionInfo.TransitionMode
@@ -2233,19 +2253,19 @@
*/
private static class Targets {
/** All targets. Its keys (depth) are sorted in ascending order naturally. */
- final SparseArray<WindowContainer<?>> mArray = new SparseArray<>();
+ final SparseArray<ChangeInfo> mArray = new SparseArray<>();
/** The targets which were represented by their parent. */
- private ArrayList<WindowContainer<?>> mRemovedTargets;
+ private ArrayList<ChangeInfo> mRemovedTargets;
private int mDepthFactor;
- void add(WindowContainer<?> target) {
+ void add(ChangeInfo target) {
// The number of slots per depth is larger than the total number of window container,
// so the depth score (key) won't have collision.
if (mDepthFactor == 0) {
- mDepthFactor = target.mWmService.mRoot.getTreeWeight() + 1;
+ mDepthFactor = target.mContainer.mWmService.mRoot.getTreeWeight() + 1;
}
- int score = target.getPrefixOrderIndex();
- WindowContainer<?> wc = target;
+ int score = target.mContainer.getPrefixOrderIndex();
+ WindowContainer<?> wc = target.mContainer;
while (wc != null) {
final WindowContainer<?> parent = wc.getParent();
if (parent != null) {
@@ -2256,7 +2276,8 @@
mArray.put(score, target);
}
- void remove(int index, WindowContainer<?> removingTarget) {
+ void remove(int index) {
+ final ChangeInfo removingTarget = mArray.valueAt(index);
mArray.removeAt(index);
if (mRemovedTargets == null) {
mRemovedTargets = new ArrayList<>();
@@ -2264,19 +2285,19 @@
mRemovedTargets.add(removingTarget);
}
- boolean wasParticipated(WindowContainer<?> wc) {
+ boolean wasParticipated(ChangeInfo wc) {
return mArray.indexOfValue(wc) >= 0
|| (mRemovedTargets != null && mRemovedTargets.contains(wc));
}
/** Returns the target list sorted by z-order in ascending order (index 0 is top). */
- ArrayList<WindowContainer> getListSortedByZ() {
- final SparseArray<WindowContainer<?>> arrayByZ = new SparseArray<>(mArray.size());
+ ArrayList<ChangeInfo> getListSortedByZ() {
+ final SparseArray<ChangeInfo> arrayByZ = new SparseArray<>(mArray.size());
for (int i = mArray.size() - 1; i >= 0; --i) {
final int zOrder = mArray.keyAt(i) % mDepthFactor;
arrayByZ.put(zOrder, mArray.valueAt(i));
}
- final ArrayList<WindowContainer> sortedTargets = new ArrayList<>(arrayByZ.size());
+ final ArrayList<ChangeInfo> sortedTargets = new ArrayList<>(arrayByZ.size());
for (int i = arrayByZ.size() - 1; i >= 0; --i) {
sortedTargets.add(arrayByZ.valueAt(i));
}
diff --git a/services/core/java/com/android/server/wm/TransitionTracer.java b/services/core/java/com/android/server/wm/TransitionTracer.java
index c1927d8..022c19b 100644
--- a/services/core/java/com/android/server/wm/TransitionTracer.java
+++ b/services/core/java/com/android/server/wm/TransitionTracer.java
@@ -109,7 +109,7 @@
final long changeEntryToken = outputStream.start(CHANGE);
final int transitMode = changeInfo.getTransitMode(window);
- final boolean hasChanged = changeInfo.hasChanged(window);
+ final boolean hasChanged = changeInfo.hasChanged();
final int changeFlags = changeInfo.getChangeFlags(window);
outputStream.write(TRANSIT_MODE, transitMode);
diff --git a/services/core/java/com/android/server/wm/WindowManagerDebugConfig.java b/services/core/java/com/android/server/wm/WindowManagerDebugConfig.java
index 42b556f..6abb8fd 100644
--- a/services/core/java/com/android/server/wm/WindowManagerDebugConfig.java
+++ b/services/core/java/com/android/server/wm/WindowManagerDebugConfig.java
@@ -56,4 +56,7 @@
static final boolean SHOW_STACK_CRAWLS = false;
static final boolean DEBUG_WINDOW_CROP = false;
static final boolean DEBUG_UNKNOWN_APP_VISIBILITY = false;
+
+ // TODO(b/239501597) : Have a system property to control this flag.
+ public static final boolean DEBUG_IME_VISIBILITY = false;
}
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index d341ef9..0012e3f 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -8657,16 +8657,17 @@
*/
void grantInputChannel(Session session, int callingUid, int callingPid, int displayId,
SurfaceControl surface, IWindow window, IBinder hostInputToken,
- int flags, int privateFlags, int type, IBinder focusGrantToken,
+ int flags, int privateFlags, int type, IBinder windowToken, IBinder focusGrantToken,
String inputHandleName, InputChannel outInputChannel) {
+ final int sanitizedType = sanitizeWindowType(session, displayId, windowToken, type);
final InputApplicationHandle applicationHandle;
final String name;
final InputChannel clientChannel;
synchronized (mGlobalLock) {
EmbeddedWindowController.EmbeddedWindow win =
new EmbeddedWindowController.EmbeddedWindow(session, this, window,
- mInputToWindowMap.get(hostInputToken), callingUid, callingPid, type,
- displayId, focusGrantToken, inputHandleName);
+ mInputToWindowMap.get(hostInputToken), callingUid, callingPid,
+ sanitizedType, displayId, focusGrantToken, inputHandleName);
clientChannel = win.openInputChannel();
mEmbeddedWindowController.add(clientChannel.getToken(), win);
applicationHandle = win.getApplicationHandle();
@@ -8674,7 +8675,8 @@
}
updateInputChannel(clientChannel.getToken(), callingUid, callingPid, displayId, surface,
- name, applicationHandle, flags, privateFlags, type, null /* region */, window);
+ name, applicationHandle, flags, privateFlags, sanitizedType,
+ null /* region */, window);
clientChannel.copyTo(outInputChannel);
}
@@ -9360,4 +9362,36 @@
public boolean isGlobalKey(int keyCode) {
return mPolicy.isGlobalKey(keyCode);
}
+
+ private int sanitizeWindowType(Session session, int displayId, IBinder windowToken, int type) {
+ // Determine whether this window type is valid for this process.
+ final boolean isTypeValid;
+ if (type == TYPE_ACCESSIBILITY_OVERLAY && windowToken != null) {
+ // Only accessibility services can add accessibility overlays.
+ // Accessibility services will have a WindowToken with type
+ // TYPE_ACCESSIBILITY_OVERLAY.
+ final DisplayContent displayContent = mRoot.getDisplayContent(displayId);
+ final WindowToken token = displayContent.getWindowToken(windowToken);
+ if (token == null) {
+ isTypeValid = false;
+ } else if (type == token.getWindowType()) {
+ isTypeValid = true;
+ } else {
+ isTypeValid = false;
+ }
+ } else if (!session.mCanAddInternalSystemWindow && type != 0) {
+ Slog.w(
+ TAG_WM,
+ "Requires INTERNAL_SYSTEM_WINDOW permission if assign type to"
+ + " input. New type will be 0.");
+ isTypeValid = false;
+ } else {
+ isTypeValid = true;
+ }
+
+ if (!isTypeValid) {
+ return 0;
+ }
+ return type;
+ }
}
diff --git a/services/credentials/java/com/android/server/credentials/CreateRequestSession.java b/services/credentials/java/com/android/server/credentials/CreateRequestSession.java
index 3d337b8..d9dc4ed 100644
--- a/services/credentials/java/com/android/server/credentials/CreateRequestSession.java
+++ b/services/credentials/java/com/android/server/credentials/CreateRequestSession.java
@@ -83,16 +83,6 @@
}
}
- private void respondToClientAndFinish(CreateCredentialResponse response) {
- Log.i(TAG, "respondToClientAndFinish");
- try {
- mClientCallback.onResponse(response);
- } catch (RemoteException e) {
- e.printStackTrace();
- }
- finishSession();
- }
-
@Override
public void onProviderStatusChanged(ProviderSession.Status status,
ComponentName componentName) {
@@ -101,10 +91,47 @@
@Override
public void onFinalResponseReceived(ComponentName componentName,
- CreateCredentialResponse response) {
+ @Nullable CreateCredentialResponse response) {
Log.i(TAG, "onFinalCredentialReceived from: " + componentName.flattenToString());
if (response != null) {
- respondToClientAndFinish(response);
+ respondToClientWithResponseAndFinish(response);
+ } else {
+ // TODO("Replace with properly defined error type)
+ respondToClientWithErrorAndFinish("unknown_type",
+ "Invalid response");
}
}
+
+ @Override
+ public void onFinalErrorReceived(ComponentName componentName, String errorType,
+ String message) {
+ respondToClientWithErrorAndFinish(errorType, message);
+ }
+
+ @Override
+ public void onUiCancellation() {
+ // TODO("Replace with properly defined error type")
+ respondToClientWithErrorAndFinish("user_cancelled",
+ "User cancelled the selector");
+ }
+
+ private void respondToClientWithResponseAndFinish(CreateCredentialResponse response) {
+ Log.i(TAG, "respondToClientWithResponseAndFinish");
+ try {
+ mClientCallback.onResponse(response);
+ } catch (RemoteException e) {
+ Log.i(TAG, "Issue while responding to client: " + e.getMessage());
+ }
+ finishSession();
+ }
+
+ private void respondToClientWithErrorAndFinish(String errorType, String errorMsg) {
+ Log.i(TAG, "respondToClientWithErrorAndFinish");
+ try {
+ mClientCallback.onError(errorType, errorMsg);
+ } catch (RemoteException e) {
+ Log.i(TAG, "Issue while responding to client: " + e.getMessage());
+ }
+ finishSession();
+ }
}
diff --git a/services/credentials/java/com/android/server/credentials/CredentialManagerService.java b/services/credentials/java/com/android/server/credentials/CredentialManagerService.java
index 1e8c465..1f74e93 100644
--- a/services/credentials/java/com/android/server/credentials/CredentialManagerService.java
+++ b/services/credentials/java/com/android/server/credentials/CredentialManagerService.java
@@ -177,12 +177,20 @@
// Initiate all provider sessions
List<ProviderSession> providerSessions =
- initiateProviderSessions(
- session,
- request.getGetCredentialOptions().stream()
- .map(GetCredentialOption::getType)
- .collect(Collectors.toList()));
- // TODO : Return error when no providers available
+ initiateProviderSessions(session, request.getGetCredentialOptions()
+ .stream().map(GetCredentialOption::getType)
+ .collect(Collectors.toList()));
+
+ if (providerSessions.isEmpty()) {
+ try {
+ // TODO("Replace with properly defined error type")
+ callback.onError("unknown_type",
+ "No providers available to fulfill request.");
+ } catch (RemoteException e) {
+ Log.i(TAG, "Issue invoking onError on IGetCredentialCallback "
+ + "callback: " + e.getMessage());
+ }
+ }
// Iterate over all provider sessions and invoke the request
providerSessions.forEach(providerGetSession -> {
@@ -214,7 +222,17 @@
// Initiate all provider sessions
List<ProviderSession> providerSessions =
initiateProviderSessions(session, List.of(request.getType()));
- // TODO : Return error when no providers available
+
+ if (providerSessions.isEmpty()) {
+ try {
+ // TODO("Replace with properly defined error type")
+ callback.onError("unknown_type",
+ "No providers available to fulfill request.");
+ } catch (RemoteException e) {
+ Log.i(TAG, "Issue invoking onError on ICreateCredentialCallback "
+ + "callback: " + e.getMessage());
+ }
+ }
// Iterate over all provider sessions and invoke the request
providerSessions.forEach(
diff --git a/services/credentials/java/com/android/server/credentials/CredentialManagerUi.java b/services/credentials/java/com/android/server/credentials/CredentialManagerUi.java
index 33c5ec9..64a5152 100644
--- a/services/credentials/java/com/android/server/credentials/CredentialManagerUi.java
+++ b/services/credentials/java/com/android/server/credentials/CredentialManagerUi.java
@@ -17,8 +17,11 @@
import android.annotation.NonNull;
import android.app.PendingIntent;
+import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
+import android.content.pm.ServiceInfo;
+import android.credentials.ui.DisabledProviderData;
import android.credentials.ui.IntentFactory;
import android.credentials.ui.ProviderData;
import android.credentials.ui.RequestInfo;
@@ -27,11 +30,14 @@
import android.os.Handler;
import android.os.Looper;
import android.os.ResultReceiver;
+import android.service.credentials.CredentialProviderInfo;
import android.util.Log;
import android.util.Slog;
import java.util.ArrayList;
+import java.util.Set;
import java.util.UUID;
+import java.util.stream.Collectors;
/** Initiates the Credential Manager UI and receives results. */
public class CredentialManagerUi {
@@ -89,8 +95,27 @@
public PendingIntent createPendingIntent(
RequestInfo requestInfo, ArrayList<ProviderData> providerDataList) {
Log.i(TAG, "In createPendingIntent");
- Intent intent = IntentFactory.newIntent(requestInfo, providerDataList, new ArrayList<>(),
- mResultReceiver)
+
+ ArrayList<DisabledProviderData> disabledProviderDataList = new ArrayList<>();
+ Set<String> enabledProviders = providerDataList.stream()
+ .map(ProviderData::getProviderFlattenedComponentName)
+ .collect(Collectors.toUnmodifiableSet());
+ // TODO("Filter out non user configurable providers")
+ Set<String> allProviders =
+ CredentialProviderInfo.getAvailableServices(mContext, mUserId).stream()
+ .map(CredentialProviderInfo::getServiceInfo)
+ .map(ServiceInfo::getComponentName)
+ .map(ComponentName::flattenToString)
+ .collect(Collectors.toUnmodifiableSet());
+
+ for (String provider: allProviders) {
+ if (!enabledProviders.contains(provider)) {
+ disabledProviderDataList.add(new DisabledProviderData(provider));
+ }
+ }
+
+ Intent intent = IntentFactory.newIntent(requestInfo, providerDataList,
+ disabledProviderDataList, mResultReceiver)
.setAction(UUID.randomUUID().toString());
//TODO: Create unique pending intent using request code and cancel any pre-existing pending
// intents
diff --git a/services/credentials/java/com/android/server/credentials/GetRequestSession.java b/services/credentials/java/com/android/server/credentials/GetRequestSession.java
index c092b3a..6e070f9 100644
--- a/services/credentials/java/com/android/server/credentials/GetRequestSession.java
+++ b/services/credentials/java/com/android/server/credentials/GetRequestSession.java
@@ -88,15 +88,27 @@
@Override
public void onFinalResponseReceived(ComponentName componentName,
- GetCredentialResponse response) {
+ @Nullable GetCredentialResponse response) {
Log.i(TAG, "onFinalCredentialReceived from: " + componentName.flattenToString());
if (response != null) {
- respondToClientAndFinish(response);
+ respondToClientWithResponseAndFinish(response);
+ } else {
+ // TODO("Replace with no credentials/unknown type when ready)
+ respondToClientWithErrorAndFinish("unknown_type",
+ "Invalid response from provider");
}
}
- private void respondToClientAndFinish(GetCredentialResponse response) {
- Log.i(TAG, "respondToClientAndFinish");
+ //TODO: Try moving the three error & response methods below to RequestSession to be shared
+ // between get & create.
+ @Override
+ public void onFinalErrorReceived(ComponentName componentName, String errorType,
+ String message) {
+ respondToClientWithErrorAndFinish(errorType, message);
+ }
+
+ private void respondToClientWithResponseAndFinish(GetCredentialResponse response) {
+ Log.i(TAG, "respondToClientWithResponseAndFinish");
try {
mClientCallback.onResponse(response);
} catch (RemoteException e) {
@@ -104,4 +116,21 @@
}
finishSession();
}
+
+ private void respondToClientWithErrorAndFinish(String errorType, String errorMsg) {
+ Log.i(TAG, "respondToClientWithErrorAndFinish");
+ try {
+ mClientCallback.onError(errorType, errorMsg);
+ } catch (RemoteException e) {
+ e.printStackTrace();
+ }
+ finishSession();
+ }
+
+ @Override
+ public void onUiCancellation() {
+ // TODO("Replace with properly defined error type")
+ respondToClientWithErrorAndFinish("user_canceled",
+ "User cancelled the selector");
+ }
}
diff --git a/services/credentials/java/com/android/server/credentials/PendingIntentResultHandler.java b/services/credentials/java/com/android/server/credentials/PendingIntentResultHandler.java
index 7f9e57a..8796314 100644
--- a/services/credentials/java/com/android/server/credentials/PendingIntentResultHandler.java
+++ b/services/credentials/java/com/android/server/credentials/PendingIntentResultHandler.java
@@ -18,7 +18,9 @@
import android.app.Activity;
import android.content.Intent;
+import android.credentials.CreateCredentialException;
import android.credentials.CreateCredentialResponse;
+import android.credentials.GetCredentialException;
import android.credentials.GetCredentialResponse;
import android.credentials.ui.ProviderPendingIntentResponse;
import android.service.credentials.CredentialProviderService;
@@ -31,7 +33,7 @@
*/
public class PendingIntentResultHandler {
/** Returns true if the result is successful and may contain result extras. */
- public static boolean isSuccessfulResponse(
+ public static boolean isValidResponse(
ProviderPendingIntentResponse pendingIntentResponse) {
//TODO: Differentiate based on extra_error in the resultData
return pendingIntentResponse.getResultCode() == Activity.RESULT_OK;
@@ -66,4 +68,28 @@
CredentialProviderService.EXTRA_GET_CREDENTIAL_RESPONSE,
GetCredentialResponse.class);
}
+
+ /** Extract the {@link CreateCredentialException} from the
+ * given pending intent . */
+ public static CreateCredentialException extractCreateCredentialException(
+ Intent resultData) {
+ if (resultData == null) {
+ return null;
+ }
+ return resultData.getParcelableExtra(
+ CredentialProviderService.EXTRA_CREATE_CREDENTIAL_EXCEPTION,
+ CreateCredentialException.class);
+ }
+
+ /** Extract the {@link GetCredentialException} from the
+ * given pending intent . */
+ public static GetCredentialException extractGetCredentialException(
+ Intent resultData) {
+ if (resultData == null) {
+ return null;
+ }
+ return resultData.getParcelableExtra(
+ CredentialProviderService.EXTRA_GET_CREDENTIAL_EXCEPTION,
+ GetCredentialException.class);
+ }
}
diff --git a/services/credentials/java/com/android/server/credentials/ProviderCreateSession.java b/services/credentials/java/com/android/server/credentials/ProviderCreateSession.java
index 32e85b0..855f274 100644
--- a/services/credentials/java/com/android/server/credentials/ProviderCreateSession.java
+++ b/services/credentials/java/com/android/server/credentials/ProviderCreateSession.java
@@ -23,6 +23,7 @@
import android.content.Context;
import android.content.Intent;
import android.content.pm.Signature;
+import android.credentials.CreateCredentialException;
import android.credentials.ui.CreateCredentialProviderData;
import android.credentials.ui.Entry;
import android.credentials.ui.ProviderPendingIntentResponse;
@@ -58,6 +59,8 @@
/** The complete request to be used in the second round. */
private final CreateCredentialRequest mCompleteRequest;
+ private CreateCredentialException mProviderException;
+
/** Creates a new provider session to be used by the request session. */
@Nullable public static ProviderCreateSession createNewSession(
Context context,
@@ -124,8 +127,11 @@
/** Called when the provider response resulted in a failure. */
@Override
- public void onProviderResponseFailure(int errorCode, @Nullable String errorType,
- @Nullable CharSequence message) {
+ public void onProviderResponseFailure(int errorCode, @Nullable Exception exception) {
+ if (exception instanceof CreateCredentialException) {
+ // Store query phase exception for aggregation with final response
+ mProviderException = (CreateCredentialException) exception;
+ }
updateStatusAndInvokeCallback(toStatus(errorCode));
}
@@ -177,16 +183,20 @@
if (mUiSaveEntries.containsKey(entryKey)) {
onSaveEntrySelected(providerPendingIntentResponse);
} else {
- //TODO: Handle properly
Log.i(TAG, "Unexpected save entry key");
+ // TODO("Replace with no credentials error type");
+ invokeCallbackWithError("unknown_type",
+ "Issue while retrieving credential");
}
break;
case REMOTE_ENTRY_KEY:
if (mUiRemoteEntry.first.equals(entryKey)) {
onRemoteEntrySelected(providerPendingIntentResponse);
} else {
- //TODO: Handle properly
Log.i(TAG, "Unexpected remote entry key");
+ // TODO("Replace with unknown/no credentials exception")
+ invokeCallbackWithError("unknown_type",
+ "Issue while retrieving credential");
}
break;
default:
@@ -227,19 +237,53 @@
}
private void onSaveEntrySelected(ProviderPendingIntentResponse pendingIntentResponse) {
- if (pendingIntentResponse == null) {
+ CreateCredentialException exception = maybeGetPendingIntentException(
+ pendingIntentResponse);
+ if (exception != null) {
+ invokeCallbackWithError(
+ exception.errorType,
+ exception.getMessage());
return;
- //TODO: Handle failure if pending intent is null
}
- if (PendingIntentResultHandler.isSuccessfulResponse(pendingIntentResponse)) {
- android.credentials.CreateCredentialResponse credentialResponse =
- PendingIntentResultHandler.extractCreateCredentialResponse(
- pendingIntentResponse.getResultData());
- if (credentialResponse != null) {
- mCallbacks.onFinalResponseReceived(mComponentName, credentialResponse);
- return;
+ android.credentials.CreateCredentialResponse credentialResponse =
+ PendingIntentResultHandler.extractCreateCredentialResponse(
+ pendingIntentResponse.getResultData());
+ if (credentialResponse != null) {
+ mCallbacks.onFinalResponseReceived(mComponentName, credentialResponse);
+ return;
+ } else {
+ Log.i(TAG, "onSaveEntrySelected - no response or error found in pending "
+ + "intent response");
+ invokeCallbackWithError(
+ // TODO("Replace with unknown/no credentials exception")
+ "unknown",
+ "Issue encountered while retrieving the credential");
+ }
+ }
+
+ private void invokeCallbackWithError(String errorType, @Nullable String message) {
+ mCallbacks.onFinalErrorReceived(mComponentName, errorType, message);
+ }
+
+ @Nullable
+ private CreateCredentialException maybeGetPendingIntentException(
+ ProviderPendingIntentResponse pendingIntentResponse) {
+ if (pendingIntentResponse == null) {
+ Log.i(TAG, "pendingIntentResponse is null");
+ return null;
+ }
+ if (PendingIntentResultHandler.isValidResponse(pendingIntentResponse)) {
+ CreateCredentialException exception = PendingIntentResultHandler
+ .extractCreateCredentialException(pendingIntentResponse.getResultData());
+ if (exception != null) {
+ Log.i(TAG, "Pending intent contains provider exception");
+ return exception;
}
+ } else {
+ Log.i(TAG, "Pending intent result code not Activity.RESULT_OK");
+ // TODO("Update with unknown exception when ready")
+ return new CreateCredentialException("unknown");
}
- //TODO: Handle failure case is pending intent response does not have a credential
+ return null;
}
}
diff --git a/services/credentials/java/com/android/server/credentials/ProviderGetSession.java b/services/credentials/java/com/android/server/credentials/ProviderGetSession.java
index 218fc21..42f872d2 100644
--- a/services/credentials/java/com/android/server/credentials/ProviderGetSession.java
+++ b/services/credentials/java/com/android/server/credentials/ProviderGetSession.java
@@ -22,7 +22,7 @@
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
-import android.content.pm.Signature;
+import android.credentials.GetCredentialException;
import android.credentials.GetCredentialOption;
import android.credentials.GetCredentialResponse;
import android.credentials.ui.Entry;
@@ -80,6 +80,8 @@
/** The complete request to be used in the second round. */
private final GetCredentialRequest mCompleteRequest;
+ private GetCredentialException mProviderException;
+
/** Creates a new provider session to be used by the request session. */
@Nullable public static ProviderGetSession createNewSession(
Context context,
@@ -131,7 +133,7 @@
if (!filteredOptions.isEmpty()) {
return new GetCredentialRequest.Builder(
new CallingAppInfo(clientCallingPackage,
- new ArraySet<Signature>())).setGetCredentialOptions(
+ new ArraySet<>())).setGetCredentialOptions(
filteredOptions).build();
}
Log.i(TAG, "In createProviderRequest - returning null");
@@ -164,8 +166,10 @@
/** Called when the provider response resulted in a failure. */
@Override // Callback from the remote provider
- public void onProviderResponseFailure(int errorCode, @Nullable String errorType,
- @Nullable CharSequence message) {
+ public void onProviderResponseFailure(int errorCode, Exception exception) {
+ if (exception instanceof GetCredentialException) {
+ mProviderException = (GetCredentialException) exception;
+ }
updateStatusAndInvokeCallback(toStatus(errorCode));
}
@@ -187,8 +191,10 @@
case CREDENTIAL_ENTRY_KEY:
CredentialEntry credentialEntry = mUiCredentialEntries.get(entryKey);
if (credentialEntry == null) {
- Log.i(TAG, "Credential entry not found");
- //TODO: Handle properly
+ Log.i(TAG, "Unexpected credential entry key");
+ // TODO("Replace with no credentials/unknown exception")
+ invokeCallbackWithError("unknown_type",
+ "Issue while retrieving credential");
return;
}
onCredentialEntrySelected(credentialEntry, providerPendingIntentResponse);
@@ -196,8 +202,10 @@
case ACTION_ENTRY_KEY:
Action actionEntry = mUiActionsEntries.get(entryKey);
if (actionEntry == null) {
- Log.i(TAG, "Action entry not found");
- //TODO: Handle properly
+ Log.i(TAG, "Unexpected action entry key");
+ // TODO("Replace with no credentials/unknown exception")
+ invokeCallbackWithError("unknown_type",
+ "Issue while retrieving credential");
return;
}
onActionEntrySelected(providerPendingIntentResponse);
@@ -206,16 +214,20 @@
if (mUiAuthenticationAction.first.equals(entryKey)) {
onAuthenticationEntrySelected(providerPendingIntentResponse);
} else {
- //TODO: Handle properly
- Log.i(TAG, "Authentication entry not found");
+ Log.i(TAG, "Unexpected authentication entry key");
+ // TODO("Replace with no credentials/unknown exception")
+ invokeCallbackWithError("unknown_type",
+ "Issue while retrieving credential");
}
break;
case REMOTE_ENTRY_KEY:
if (mUiRemoteEntry.first.equals(entryKey)) {
onRemoteEntrySelected(providerPendingIntentResponse);
} else {
- //TODO: Handle properly
- Log.i(TAG, "Remote entry not found");
+ Log.i(TAG, "Unexpected remote entry key");
+ // TODO("Replace with no credentials/unknown exception")
+ invokeCallbackWithError("unknown_type",
+ "Issue while retrieving credential");
}
break;
default:
@@ -223,6 +235,11 @@
}
}
+ private void invokeCallbackWithError(String errorType, @Nullable String errorMessage) {
+ // TODO: Determine what the error message should be
+ mCallbacks.onFinalErrorReceived(mComponentName, errorType, errorMessage);
+ }
+
@Override // Call from request session to data to be shown on the UI
@Nullable protected GetCredentialProviderData prepareUiData() throws IllegalArgumentException {
Log.i(TAG, "In prepareUiData");
@@ -284,10 +301,9 @@
mUiCredentialEntries.put(entryId, credentialEntry);
Log.i(TAG, "in prepareUiProviderData creating ui entry with id " + entryId);
if (credentialEntry.getPendingIntent() != null) {
- setUpFillInIntent(credentialEntry.getPendingIntent());
credentialUiEntries.add(new Entry(CREDENTIAL_ENTRY_KEY, entryId,
credentialEntry.getSlice(), credentialEntry.getPendingIntent(),
- /*fillInIntent=*/null));
+ /*fillInIntent=*/setUpFillInIntent(credentialEntry.getPendingIntent())));
} else {
Log.i(TAG, "No pending intent. Should not happen.");
}
@@ -330,39 +346,61 @@
private void onCredentialEntrySelected(CredentialEntry credentialEntry,
ProviderPendingIntentResponse providerPendingIntentResponse) {
if (providerPendingIntentResponse != null) {
- if (PendingIntentResultHandler.isSuccessfulResponse(providerPendingIntentResponse)) {
- // TODO: Remove credential extraction when flow is fully transitioned
- GetCredentialResponse getCredentialResponse = PendingIntentResultHandler
- .extractGetCredentialResponse(
- providerPendingIntentResponse.getResultData());
- if (getCredentialResponse != null) {
- mCallbacks.onFinalResponseReceived(mComponentName, getCredentialResponse);
- return;
- }
+ // Check if pending intent has an error
+ GetCredentialException exception = maybeGetPendingIntentException(
+ providerPendingIntentResponse);
+ if (exception != null) {
+ invokeCallbackWithError(exception.errorType,
+ exception.getMessage());
+ return;
}
- // TODO: Handle other pending intent statuses
+
+ // Check if pending intent has a credential
+ GetCredentialResponse getCredentialResponse = PendingIntentResultHandler
+ .extractGetCredentialResponse(
+ providerPendingIntentResponse.getResultData());
+ if (getCredentialResponse != null) {
+ mCallbacks.onFinalResponseReceived(mComponentName,
+ getCredentialResponse);
+ return;
+ }
+
+ Log.i(TAG, "Pending intent response contains no credential, or error");
+ // TODO("Replace with no credentials/unknown error when ready)
+ invokeCallbackWithError("unknown_type",
+ "Issue while retrieving credential");
}
Log.i(TAG, "CredentialEntry does not have a credential or a pending intent result");
- // TODO: Propagate failure to client
+ // TODO("Replace with no credentials/unknown error when ready)
+ invokeCallbackWithError("unknown_type",
+ "Error encountered while retrieving the credential");
}
private void onAuthenticationEntrySelected(
@Nullable ProviderPendingIntentResponse providerPendingIntentResponse) {
- if (providerPendingIntentResponse != null) {
- if (PendingIntentResultHandler.isSuccessfulResponse(providerPendingIntentResponse)) {
- CredentialsResponseContent content = PendingIntentResultHandler
- .extractResponseContent(providerPendingIntentResponse
- .getResultData());
- if (content != null) {
- onUpdateResponse(
- BeginGetCredentialResponse.createWithResponseContent(content));
- return;
- }
- }
//TODO: Other provider intent statuses
+ // Check if pending intent has an error
+ GetCredentialException exception = maybeGetPendingIntentException(
+ providerPendingIntentResponse);
+ if (exception != null) {
+ invokeCallbackWithError(exception.errorType,
+ exception.getMessage());
+ return;
}
- Log.i(TAG, "Display content not present in pending intent result");
- // TODO: Propagate error to client
+
+ // Check if pending intent has the content
+ CredentialsResponseContent content = PendingIntentResultHandler
+ .extractResponseContent(providerPendingIntentResponse
+ .getResultData());
+ if (content != null) {
+ onUpdateResponse(BeginGetCredentialResponse.createWithResponseContent(content));
+ return;
+ }
+
+ Log.i(TAG, "No error or respond found in pending intent response");
+ // TODO("Replace with no credentials/unknown error when ready)
+ invokeCallbackWithError("unknown type", "Issue"
+ + " while retrieving credential");
}
private void onActionEntrySelected(ProviderPendingIntentResponse
@@ -383,4 +421,26 @@
updateStatusAndInvokeCallback(Status.CREDENTIALS_RECEIVED);
}
}
+
+ @Nullable
+ private GetCredentialException maybeGetPendingIntentException(
+ ProviderPendingIntentResponse pendingIntentResponse) {
+ if (pendingIntentResponse == null) {
+ Log.i(TAG, "pendingIntentResponse is null");
+ return null;
+ }
+ if (PendingIntentResultHandler.isValidResponse(pendingIntentResponse)) {
+ GetCredentialException exception = PendingIntentResultHandler
+ .extractGetCredentialException(pendingIntentResponse.getResultData());
+ if (exception != null) {
+ Log.i(TAG, "Pending intent contains provider exception");
+ return exception;
+ }
+ } else {
+ Log.i(TAG, "Pending intent result code not Activity.RESULT_OK");
+ // TODO("Update with unknown exception when ready")
+ return new GetCredentialException("unknown");
+ }
+ return null;
+ }
}
diff --git a/services/credentials/java/com/android/server/credentials/ProviderSession.java b/services/credentials/java/com/android/server/credentials/ProviderSession.java
index ac360bd..7ecae9d 100644
--- a/services/credentials/java/com/android/server/credentials/ProviderSession.java
+++ b/services/credentials/java/com/android/server/credentials/ProviderSession.java
@@ -24,7 +24,6 @@
import android.credentials.ui.ProviderData;
import android.credentials.ui.ProviderPendingIntentResponse;
import android.service.credentials.CredentialEntry;
-import android.service.credentials.CredentialProviderException;
import android.service.credentials.CredentialProviderInfo;
import android.util.Pair;
@@ -37,6 +36,8 @@
*/
public abstract class ProviderSession<T, R>
implements RemoteCredentialService.ProviderCallbacks<R> {
+
+ private static final String TAG = "ProviderSession";
// Key to be used as an entry key for a remote entry
protected static final String REMOTE_ENTRY_KEY = "remote_entry_key";
@@ -52,6 +53,7 @@
@Nullable protected R mProviderResponse;
@Nullable protected Pair<String, CredentialEntry> mUiRemoteEntry;
+
/**
* Returns true if the given status reflects that the provider state is ready to be shown
* on the credMan UI.
@@ -95,8 +97,12 @@
/** Called when status changes. */
void onProviderStatusChanged(Status status, ComponentName componentName);
- /** Called when the final credential to be returned to the client has been received. */
+ /** Called when the final credential is received through an entry selection. */
void onFinalResponseReceived(ComponentName componentName, V response);
+
+ /** Called when an error is received through an entry selection. */
+ void onFinalErrorReceived(ComponentName componentName, String errorType,
+ @Nullable String message);
}
protected ProviderSession(@NonNull Context context, @NonNull CredentialProviderInfo info,
@@ -129,8 +135,7 @@
/** Converts exception to a provider session status. */
@NonNull
- public static Status toStatus(
- @CredentialProviderException.CredentialProviderError int errorCode) {
+ public static Status toStatus(int errorCode) {
// TODO : Add more mappings as more flows are supported
return Status.CANCELED;
}
diff --git a/services/credentials/java/com/android/server/credentials/RemoteCredentialService.java b/services/credentials/java/com/android/server/credentials/RemoteCredentialService.java
index 307d96a..6049fd9 100644
--- a/services/credentials/java/com/android/server/credentials/RemoteCredentialService.java
+++ b/services/credentials/java/com/android/server/credentials/RemoteCredentialService.java
@@ -30,7 +30,7 @@
import android.service.credentials.BeginCreateCredentialResponse;
import android.service.credentials.BeginGetCredentialRequest;
import android.service.credentials.BeginGetCredentialResponse;
-import android.service.credentials.CredentialProviderException;
+import android.service.credentials.CredentialProviderErrors;
import android.service.credentials.CredentialProviderService;
import android.service.credentials.IBeginCreateCredentialCallback;
import android.service.credentials.IBeginGetCredentialCallback;
@@ -72,8 +72,7 @@
/** Called when a successful response is received from the remote provider. */
void onProviderResponseSuccess(@Nullable T response);
/** Called when a failure response is received from the remote provider. */
- void onProviderResponseFailure(int errorCode, @Nullable String errorType,
- @Nullable CharSequence message);
+ void onProviderResponseFailure(int internalErrorCode, @Nullable Exception e);
/** Called when the remote provider service dies. */
void onProviderServiceDied(RemoteCredentialService service);
}
@@ -208,36 +207,31 @@
Log.i(TAG, "In RemoteCredentialService execute error is timeout");
dispatchCancellationSignal(cancellationSink.get());
callback.onProviderResponseFailure(
- CredentialProviderException.ERROR_TIMEOUT,
- null,
- error.getMessage());
+ CredentialProviderErrors.ERROR_TIMEOUT,
+ null);
} else if (error instanceof CancellationException) {
Log.i(TAG, "In RemoteCredentialService execute error is cancellation");
dispatchCancellationSignal(cancellationSink.get());
callback.onProviderResponseFailure(
- CredentialProviderException.ERROR_TASK_CANCELED,
- null,
- error.getMessage());
+ CredentialProviderErrors.ERROR_TASK_CANCELED,
+ null);
} else if (error instanceof GetCredentialException) {
Log.i(TAG, "In RemoteCredentialService execute error is provider get"
+ "error");
callback.onProviderResponseFailure(
- CredentialProviderException.ERROR_PROVIDER_FAILURE,
- ((GetCredentialException) error).errorType,
- error.getMessage());
+ CredentialProviderErrors.ERROR_PROVIDER_FAILURE,
+ (GetCredentialException) error);
} else if (error instanceof CreateCredentialException) {
Log.i(TAG, "In RemoteCredentialService execute error is provider create "
+ "error");
callback.onProviderResponseFailure(
- CredentialProviderException.ERROR_PROVIDER_FAILURE,
- ((CreateCredentialException) error).errorType,
- error.getMessage());
+ CredentialProviderErrors.ERROR_PROVIDER_FAILURE,
+ (CreateCredentialException) error);
} else {
Log.i(TAG, "In RemoteCredentialService execute error is unknown");
callback.onProviderResponseFailure(
- CredentialProviderException.ERROR_UNKNOWN,
- null,
- error.getMessage());
+ CredentialProviderErrors.ERROR_UNKNOWN,
+ (Exception) error);
}
}
}
diff --git a/services/credentials/java/com/android/server/credentials/RequestSession.java b/services/credentials/java/com/android/server/credentials/RequestSession.java
index 71fc67c..937fac9 100644
--- a/services/credentials/java/com/android/server/credentials/RequestSession.java
+++ b/services/credentials/java/com/android/server/credentials/RequestSession.java
@@ -93,6 +93,7 @@
@Override // from CredentialManagerUiCallbacks
public void onUiCancellation() {
+ Log.i(TAG, "Ui canceled");
// User canceled the activity
finishSession();
}
@@ -133,10 +134,12 @@
}
protected void finishSession() {
+ Log.i(TAG, "finishing session");
clearProviderSessions();
}
protected void clearProviderSessions() {
+ Log.i(TAG, "Clearing sessions");
//TODO: Implement
mProviders.clear();
}
@@ -151,6 +154,9 @@
}
private void getProviderDataAndInitiateUi() {
+ Log.i(TAG, "In getProviderDataAndInitiateUi");
+ Log.i(TAG, "In getProviderDataAndInitiateUi providers size: " + mProviders.size());
+
ArrayList<ProviderData> providerDataList = new ArrayList<>();
for (ProviderSession session : mProviders.values()) {
Log.i(TAG, "preparing data for : " + session.getComponentName());
diff --git a/services/permission/java/com/android/server/permission/access/AccessCheckingService.kt b/services/permission/java/com/android/server/permission/access/AccessCheckingService.kt
index f493b89..3b277f8 100644
--- a/services/permission/java/com/android/server/permission/access/AccessCheckingService.kt
+++ b/services/permission/java/com/android/server/permission/access/AccessCheckingService.kt
@@ -17,6 +17,10 @@
package com.android.server.permission.access
import android.content.Context
+import android.content.pm.PackageManager
+import android.content.pm.PackageManagerInternal
+import android.os.SystemProperties
+import android.os.UserHandle
import com.android.internal.annotations.Keep
import com.android.server.LocalManagerRegistry
import com.android.server.LocalServices
@@ -24,12 +28,16 @@
import com.android.server.SystemService
import com.android.server.appop.AppOpsCheckingServiceInterface
import com.android.server.permission.access.appop.AppOpService
-import com.android.server.permission.access.collection.IntSet
+import com.android.server.permission.access.collection.* // ktlint-disable no-wildcard-imports
import com.android.server.permission.access.permission.PermissionService
+import com.android.server.pm.KnownPackages
import com.android.server.pm.PackageManagerLocal
import com.android.server.pm.UserManagerService
import com.android.server.pm.permission.PermissionManagerServiceInterface
import com.android.server.pm.pkg.PackageState
+import kotlin.contracts.ExperimentalContracts
+import kotlin.contracts.InvocationKind
+import kotlin.contracts.contract
@Keep
class AccessCheckingService(context: Context) : SystemService(context) {
@@ -44,6 +52,7 @@
private lateinit var appOpService: AppOpService
private lateinit var permissionService: PermissionService
+ private lateinit var packageManagerInternal: PackageManagerInternal
private lateinit var packageManagerLocal: PackageManagerLocal
private lateinit var userManagerService: UserManagerService
private lateinit var systemConfig: SystemConfig
@@ -57,6 +66,7 @@
}
fun initialize() {
+ packageManagerInternal = LocalServices.getService(PackageManagerInternal::class.java)
packageManagerLocal =
LocalManagerRegistry.getManagerOrThrow(PackageManagerLocal::class.java)
userManagerService = UserManagerService.getInstance()
@@ -64,19 +74,94 @@
val userIds = IntSet(userManagerService.userIdsIncludingPreCreated)
val (packageStates, disabledSystemPackageStates) = packageManagerLocal.allPackageStates
+ val knownPackages = packageManagerInternal.knownPackages
+ val isLeanback = systemConfig.isLeanback
+ val configPermissions = systemConfig.permissions
+ val privilegedPermissionAllowlistPackages =
+ systemConfig.privilegedPermissionAllowlistPackages
val permissionAllowlist = systemConfig.permissionAllowlist
+ val implicitToSourcePermissions = systemConfig.implicitToSourcePermissions
val state = AccessState()
policy.initialize(
- state, userIds, packageStates, disabledSystemPackageStates, permissionAllowlist
+ state, userIds, packageStates, disabledSystemPackageStates, knownPackages, isLeanback,
+ configPermissions, privilegedPermissionAllowlistPackages, permissionAllowlist,
+ implicitToSourcePermissions
)
persistence.read(state)
this.state = state
+ mutateState {
+ with(policy) { onInitialized() }
+ }
+
appOpService.initialize()
permissionService.initialize()
}
+ private val PackageManagerInternal.knownPackages: IntMap<Array<String>>
+ get() = IntMap<Array<String>>().apply {
+ this[KnownPackages.PACKAGE_INSTALLER] = getKnownPackageNames(
+ KnownPackages.PACKAGE_INSTALLER, UserHandle.USER_SYSTEM
+ )
+ this[KnownPackages.PACKAGE_PERMISSION_CONTROLLER] = getKnownPackageNames(
+ KnownPackages.PACKAGE_PERMISSION_CONTROLLER, UserHandle.USER_SYSTEM
+ )
+ this[KnownPackages.PACKAGE_VERIFIER] = getKnownPackageNames(
+ KnownPackages.PACKAGE_VERIFIER, UserHandle.USER_SYSTEM
+ )
+ this[KnownPackages.PACKAGE_SETUP_WIZARD] = getKnownPackageNames(
+ KnownPackages.PACKAGE_SETUP_WIZARD, UserHandle.USER_SYSTEM
+ )
+ this[KnownPackages.PACKAGE_SYSTEM_TEXT_CLASSIFIER] = getKnownPackageNames(
+ KnownPackages.PACKAGE_SYSTEM_TEXT_CLASSIFIER, UserHandle.USER_SYSTEM
+ )
+ this[KnownPackages.PACKAGE_CONFIGURATOR] = getKnownPackageNames(
+ KnownPackages.PACKAGE_CONFIGURATOR, UserHandle.USER_SYSTEM
+ )
+ this[KnownPackages.PACKAGE_INCIDENT_REPORT_APPROVER] = getKnownPackageNames(
+ KnownPackages.PACKAGE_INCIDENT_REPORT_APPROVER, UserHandle.USER_SYSTEM
+ )
+ this[KnownPackages.PACKAGE_APP_PREDICTOR] = getKnownPackageNames(
+ KnownPackages.PACKAGE_APP_PREDICTOR, UserHandle.USER_SYSTEM
+ )
+ this[KnownPackages.PACKAGE_COMPANION] = getKnownPackageNames(
+ KnownPackages.PACKAGE_COMPANION, UserHandle.USER_SYSTEM
+ )
+ this[KnownPackages.PACKAGE_RETAIL_DEMO] = getKnownPackageNames(
+ KnownPackages.PACKAGE_RETAIL_DEMO, UserHandle.USER_SYSTEM
+ )
+ this[KnownPackages.PACKAGE_RECENTS] = getKnownPackageNames(
+ KnownPackages.PACKAGE_RECENTS, UserHandle.USER_SYSTEM
+ )
+ }
+
+ private val SystemConfig.isLeanback: Boolean
+ get() = PackageManager.FEATURE_LEANBACK in availableFeatures
+
+ private val SystemConfig.privilegedPermissionAllowlistPackages: IndexedListSet<String>
+ get() = IndexedListSet<String>().apply {
+ this += "android"
+ if (PackageManager.FEATURE_AUTOMOTIVE in availableFeatures) {
+ // Note that SystemProperties.get(String, String) forces returning an empty string
+ // even if we pass null for the def parameter.
+ val carServicePackage = SystemProperties.get("ro.android.car.carservice.package")
+ if (carServicePackage.isNotEmpty()) {
+ this += carServicePackage
+ }
+ }
+ }
+
+ private val SystemConfig.implicitToSourcePermissions: IndexedMap<String, IndexedListSet<String>>
+ get() = IndexedMap<String, IndexedListSet<String>>().apply {
+ splitPermissions.forEach { splitPermissionInfo ->
+ val sourcePermissionName = splitPermissionInfo.splitPermission
+ splitPermissionInfo.newPermissions.forEach { implicitPermissionName ->
+ getOrPut(implicitPermissionName) { IndexedListSet() } += sourcePermissionName
+ }
+ }
+ }
+
fun getDecision(subject: AccessUri, `object`: AccessUri): Int =
getState {
with(policy) { getDecision(subject, `object`) }
@@ -151,16 +236,22 @@
Pair<Map<String, PackageState>, Map<String, PackageState>>
get() = withUnfilteredSnapshot().use { it.packageStates to it.disabledSystemPackageStates }
- internal inline fun <T> getState(action: GetStateScope.() -> T): T =
- GetStateScope(state).action()
+ @OptIn(ExperimentalContracts::class)
+ internal inline fun <T> getState(action: GetStateScope.() -> T): T {
+ contract { callsInPlace(action, InvocationKind.EXACTLY_ONCE) }
+ return GetStateScope(state).action()
+ }
+ @OptIn(ExperimentalContracts::class)
internal inline fun mutateState(crossinline action: MutateStateScope.() -> Unit) {
+ contract { callsInPlace(action, InvocationKind.EXACTLY_ONCE) }
synchronized(stateLock) {
val oldState = state
val newState = oldState.copy()
MutateStateScope(oldState, newState).action()
persistence.write(newState)
state = newState
+ with(policy) { GetStateScope(newState).onStateMutated() }
}
}
diff --git a/services/permission/java/com/android/server/permission/access/AccessPolicy.kt b/services/permission/java/com/android/server/permission/access/AccessPolicy.kt
index 8027b50..2d83bfd 100644
--- a/services/permission/java/com/android/server/permission/access/AccessPolicy.kt
+++ b/services/permission/java/com/android/server/permission/access/AccessPolicy.kt
@@ -19,6 +19,7 @@
import android.util.Log
import com.android.modules.utils.BinaryXmlPullParser
import com.android.modules.utils.BinaryXmlSerializer
+import com.android.server.SystemConfig
import com.android.server.permission.access.appop.PackageAppOpPolicy
import com.android.server.permission.access.appop.UidAppOpPolicy
import com.android.server.permission.access.collection.* // ktlint-disable no-wildcard-imports
@@ -59,7 +60,12 @@
userIds: IntSet,
packageStates: Map<String, PackageState>,
disabledSystemPackageStates: Map<String, PackageState>,
- permissionAllowlist: PermissionAllowlist
+ knownPackages: IntMap<Array<String>>,
+ isLeanback: Boolean,
+ configPermissions: Map<String, SystemConfig.PermissionEntry>,
+ privilegedPermissionAllowlistPackages: IndexedListSet<String>,
+ permissionAllowlist: PermissionAllowlist,
+ implicitToSourcePermissions: IndexedMap<String, IndexedListSet<String>>
) {
state.systemState.apply {
this.userIds += userIds
@@ -69,7 +75,24 @@
appIds.getOrPut(packageState.appId) { IndexedListSet() }
.add(packageState.packageName)
}
+ this.knownPackages = knownPackages
+ this.isLeanback = isLeanback
+ this.configPermissions = configPermissions
+ this.privilegedPermissionAllowlistPackages = privilegedPermissionAllowlistPackages
this.permissionAllowlist = permissionAllowlist
+ this.implicitToSourcePermissions = implicitToSourcePermissions
+ }
+ }
+
+ fun GetStateScope.onStateMutated() {
+ forEachSchemePolicy {
+ with(it) { onStateMutated() }
+ }
+ }
+
+ fun MutateStateScope.onInitialized() {
+ forEachSchemePolicy {
+ with(it) { onInitialized() }
}
}
@@ -284,6 +307,10 @@
decision: Int
)
+ open fun GetStateScope.onStateMutated() {}
+
+ open fun MutateStateScope.onInitialized() {}
+
open fun MutateStateScope.onUserAdded(userId: Int) {}
open fun MutateStateScope.onUserRemoved(userId: Int) {}
diff --git a/services/permission/java/com/android/server/permission/access/AccessState.kt b/services/permission/java/com/android/server/permission/access/AccessState.kt
index 71347d2..9616193 100644
--- a/services/permission/java/com/android/server/permission/access/AccessState.kt
+++ b/services/permission/java/com/android/server/permission/access/AccessState.kt
@@ -17,6 +17,7 @@
package com.android.server.permission.access
import android.content.pm.PermissionGroupInfo
+import com.android.server.SystemConfig
import com.android.server.permission.access.collection.* // ktlint-disable no-wildcard-imports
import com.android.server.permission.access.permission.Permission
import com.android.server.pm.permission.PermissionAllowlist
@@ -42,15 +43,15 @@
var packageStates: Map<String, PackageState>,
var disabledSystemPackageStates: Map<String, PackageState>,
val appIds: IntMap<IndexedListSet<String>>,
- // A map of KnownPackagesInt to a set of known package names
- val knownPackages: IntMap<IndexedListSet<String>>,
- // A map of userId to packageName
- val deviceAndProfileOwners: IntMap<String>,
- // Whether the device supports leanback UI
+ // Mapping from KnownPackages keys to package names.
+ var knownPackages: IntMap<Array<String>>,
var isLeanback: Boolean,
- val privilegedPermissionAllowlistSourcePackageNames: IndexedListSet<String>,
+ var configPermissions: Map<String, SystemConfig.PermissionEntry>,
+ var privilegedPermissionAllowlistPackages: IndexedListSet<String>,
var permissionAllowlist: PermissionAllowlist,
- val implicitToSourcePermissions: Map<String, Set<String>>,
+ var implicitToSourcePermissions: IndexedMap<String, IndexedListSet<String>>,
+ // Mapping from user ID to package name.
+ var deviceAndProfileOwners: IntMap<String>,
val permissionGroups: IndexedMap<String, PermissionGroupInfo>,
val permissionTrees: IndexedMap<String, Permission>,
val permissions: IndexedMap<String, Permission>
@@ -61,11 +62,12 @@
emptyMap(),
IntMap(),
IntMap(),
- IntMap(),
false,
+ emptyMap(),
IndexedListSet(),
PermissionAllowlist(),
IndexedMap(),
+ IntMap(),
IndexedMap(),
IndexedMap(),
IndexedMap()
@@ -77,12 +79,13 @@
packageStates,
disabledSystemPackageStates,
appIds.copy { it.copy() },
- knownPackages.copy { it.copy() },
- deviceAndProfileOwners.copy { it },
+ knownPackages,
isLeanback,
- privilegedPermissionAllowlistSourcePackageNames.copy(),
+ configPermissions,
+ privilegedPermissionAllowlistPackages,
permissionAllowlist,
implicitToSourcePermissions,
+ deviceAndProfileOwners,
permissionGroups.copy { it },
permissionTrees.copy { it },
permissions.copy { it }
@@ -92,7 +95,9 @@
class UserState private constructor(
// A map of (appId to a map of (permissionName to permissionFlags))
val uidPermissionFlags: IntMap<IndexedMap<String, Int>>,
+ // appId -> opName -> opCode
val uidAppOpModes: IntMap<IndexedMap<String, Int>>,
+ // packageName -> opName -> opCode
val packageAppOpModes: IndexedMap<String, IndexedMap<String, Int>>
) : WritableState() {
constructor() : this(
diff --git a/services/permission/java/com/android/server/permission/access/appop/AppOpService.kt b/services/permission/java/com/android/server/permission/access/appop/AppOpService.kt
index b8d6aa3..f2cff62 100644
--- a/services/permission/java/com/android/server/permission/access/appop/AppOpService.kt
+++ b/services/permission/java/com/android/server/permission/access/appop/AppOpService.kt
@@ -16,88 +16,214 @@
package com.android.server.permission.access.appop
-import android.util.ArraySet
+import android.Manifest
+import android.annotation.UserIdInt
+import android.app.AppGlobals
+import android.app.AppOpsManager
+import android.content.pm.PackageManager
+import android.os.Binder
+import android.os.Handler
+import android.os.RemoteException
+import android.os.UserHandle
import android.util.SparseBooleanArray
import android.util.SparseIntArray
+import com.android.internal.util.ArrayUtils
+import com.android.internal.util.function.pooled.PooledLambda
import com.android.server.appop.AppOpsCheckingServiceInterface
import com.android.server.appop.OnOpModeChangedListener
import com.android.server.permission.access.AccessCheckingService
+import com.android.server.permission.access.AppOpUri
+import com.android.server.permission.access.PackageUri
+import com.android.server.permission.access.UidUri
+import com.android.server.permission.access.collection.* // ktlint-disable no-wildcard-imports
+import com.android.server.permission.access.util.hasBits
+import libcore.util.EmptyArray
import java.io.PrintWriter
class AppOpService(
private val service: AccessCheckingService
) : AppOpsCheckingServiceInterface {
+ private val packagePolicy = service.getSchemePolicy(PackageUri.SCHEME, AppOpUri.SCHEME)
+ as PackageAppOpPolicy
+ private val uidPolicy = service.getSchemePolicy(UidUri.SCHEME, AppOpUri.SCHEME)
+ as UidAppOpPolicy
+
+ private val context = service.context
+ private lateinit var handler: Handler
+ private lateinit var lock: Any
+ private lateinit var switchedOps: IntMap<IntArray>
+
fun initialize() {
- TODO("Not yet implemented")
+ // TODO(b/252883039): Wrong handler. Inject main thread handler here.
+ handler = Handler(context.mainLooper)
+ // TODO(b/252883039): Wrong lock object. Inject AppOpsService here.
+ lock = Any()
+
+ switchedOps = IntMap()
+ for (switchedCode in 0 until AppOpsManager._NUM_OP) {
+ val switchCode = AppOpsManager.opToSwitch(switchedCode)
+ switchedOps.put(switchCode,
+ ArrayUtils.appendInt(switchedOps.get(switchCode), switchedCode))
+ }
}
override fun getNonDefaultUidModes(uid: Int): SparseIntArray {
- TODO("Not yet implemented")
+ return opNameMapToOpIntMap(getUidModes(uid))
}
override fun getUidMode(uid: Int, op: Int): Int {
- TODO("Not yet implemented")
+ val appId = UserHandle.getAppId(uid)
+ val userId = UserHandle.getUserId(uid)
+ val opName = AppOpsManager.opToPublicName(op)
+ return service.getState {
+ with(uidPolicy) { getAppOpMode(appId, userId, opName) }
+ }
+ }
+
+ private fun getUidModes(uid: Int): IndexedMap<String, Int>? {
+ val appId = UserHandle.getAppId(uid)
+ val userId = UserHandle.getUserId(uid)
+ return service.getState {
+ with(uidPolicy) { getAppOpModes(appId, userId) }
+ }
}
override fun setUidMode(uid: Int, op: Int, mode: Int): Boolean {
- TODO("Not yet implemented")
+ val appId = UserHandle.getAppId(uid)
+ val userId = UserHandle.getUserId(uid)
+ val opName = AppOpsManager.opToPublicName(op)
+ var wasChanged = false
+ service.mutateState {
+ wasChanged = with(uidPolicy) { setAppOpMode(appId, userId, opName, mode) }
+ }
+ return wasChanged
}
override fun getPackageMode(packageName: String, op: Int, userId: Int): Int {
- TODO("Not yet implemented")
+ val opName = AppOpsManager.opToPublicName(op)
+ return service.getState {
+ with(packagePolicy) { getAppOpMode(packageName, userId, opName) }
+ }
}
+ private fun getPackageModes(
+ packageName: String,
+ userId: Int
+ ): IndexedMap<String, Int>? =
+ service.getState { with(packagePolicy) { getAppOpModes(packageName, userId) } }
+
override fun setPackageMode(packageName: String, op: Int, mode: Int, userId: Int) {
- TODO("Not yet implemented")
- }
-
- override fun removePackage(packageName: String, userId: Int): Boolean {
- TODO("Not yet implemented")
+ val opName = AppOpsManager.opToPublicName(op)
+ service.mutateState {
+ with(packagePolicy) { setAppOpMode(packageName, userId, opName, mode) }
+ }
}
override fun removeUid(uid: Int) {
- TODO("Not yet implemented")
+ val appId = UserHandle.getAppId(uid)
+ val userId = UserHandle.getUserId(uid)
+ service.mutateState {
+ with(uidPolicy) { removeAppOpModes(appId, userId) }
+ }
}
+ override fun removePackage(packageName: String, userId: Int): Boolean {
+ var wasChanged = false
+ service.mutateState {
+ wasChanged = with (packagePolicy) { removeAppOpModes(packageName, userId) }
+ }
+ return wasChanged
+ }
+
+ private fun opNameMapToOpIntMap(modes: IndexedMap<String, Int>?): SparseIntArray =
+ if (modes == null) {
+ SparseIntArray()
+ } else {
+ val opIntMap = SparseIntArray(modes.size)
+ modes.forEachIndexed { _, opName, opMode ->
+ opIntMap.put(AppOpsManager.strOpToOp(opName), opMode)
+ }
+ opIntMap
+ }
+
override fun areUidModesDefault(uid: Int): Boolean {
- TODO("Not yet implemented")
+ val modes = getUidModes(uid)
+ return modes == null || modes.isEmpty()
}
override fun arePackageModesDefault(packageName: String, userId: Int): Boolean {
- TODO("Not yet implemented")
+ val modes = service.getState { getPackageModes(packageName, userId) }
+ return modes == null || modes.isEmpty()
}
override fun clearAllModes() {
- TODO("Not yet implemented")
+ // We don't need to implement this because it's only called in AppOpsService#readState
+ // and we have our own persistence.
}
+ // code -> listeners
+ private val opModeWatchers = IntMap<IndexedSet<OnOpModeChangedListener>>()
+
+ // packageName -> listeners
+ private val packageModeWatchers = IndexedMap<String, IndexedSet<OnOpModeChangedListener>>()
+
override fun startWatchingOpModeChanged(changedListener: OnOpModeChangedListener, op: Int) {
- TODO("Not yet implemented")
+ synchronized(lock) {
+ opModeWatchers.getOrPut(op) { IndexedSet() } += changedListener
+ }
}
override fun startWatchingPackageModeChanged(
changedListener: OnOpModeChangedListener,
packageName: String
) {
- TODO("Not yet implemented")
+ synchronized(lock) {
+ packageModeWatchers.getOrPut(packageName) { IndexedSet() } += changedListener
+ }
}
override fun removeListener(changedListener: OnOpModeChangedListener) {
- TODO("Not yet implemented")
+ synchronized(lock) {
+ opModeWatchers.removeAllIndexed { _, _, listeners ->
+ listeners -= changedListener
+ listeners.isEmpty()
+ }
+ packageModeWatchers.removeAllIndexed { _, _, listeners ->
+ listeners -= changedListener
+ listeners.isEmpty()
+ }
+ }
}
- override fun getOpModeChangedListeners(op: Int): ArraySet<OnOpModeChangedListener> {
- TODO("Not yet implemented")
+ override fun getOpModeChangedListeners(op: Int): IndexedSet<OnOpModeChangedListener> {
+ synchronized(lock) {
+ val listeners = opModeWatchers[op]
+ return if (listeners == null) {
+ IndexedSet()
+ } else {
+ IndexedSet(listeners)
+ }
+ }
}
override fun getPackageModeChangedListeners(
packageName: String
- ): ArraySet<OnOpModeChangedListener> {
- TODO("Not yet implemented")
+ ): IndexedSet<OnOpModeChangedListener> {
+ synchronized(lock) {
+ val listeners = packageModeWatchers[packageName]
+ return if (listeners == null) {
+ IndexedSet()
+ } else {
+ IndexedSet(listeners)
+ }
+ }
}
override fun notifyWatchersOfChange(op: Int, uid: Int) {
- TODO("Not yet implemented")
+ val listeners = getOpModeChangedListeners(op)
+ listeners.forEachIndexed { _, listener ->
+ notifyOpChanged(listener, op, uid, null)
+ }
}
override fun notifyOpChanged(
@@ -106,31 +232,159 @@
uid: Int,
packageName: String?
) {
- TODO("Not yet implemented")
+ if (uid != UID_ANY &&
+ changedListener.watchingUid >= 0 &&
+ changedListener.watchingUid != uid
+ ) {
+ return
+ }
+
+ // See CALL_BACK_ON_CHANGED_LISTENER_WITH_SWITCHED_OP_CHANGE
+ val switchedCodes = when (changedListener.watchedOpCode) {
+ ALL_OPS -> switchedOps.get(op)
+ AppOpsManager.OP_NONE -> intArrayOf(op)
+ else -> intArrayOf(changedListener.watchedOpCode)
+ }
+
+ for (switchedCode in switchedCodes) {
+ // There are features watching for mode changes such as window manager
+ // and location manager which are in our process. The callbacks in these
+ // features may require permissions our remote caller does not have.
+ val identity = Binder.clearCallingIdentity()
+ try {
+ if (!shouldIgnoreCallback(switchedCode, changedListener)) {
+ changedListener.onOpModeChanged(switchedCode, uid, packageName)
+ }
+ } catch (e: RemoteException) {
+ /* ignore */
+ } finally {
+ Binder.restoreCallingIdentity(identity)
+ }
+ }
}
+ private fun shouldIgnoreCallback(op: Int, listener: OnOpModeChangedListener): Boolean {
+ // If it's a restricted read op, ignore it if watcher doesn't have manage ops permission,
+ // as watcher should not use this to signal if the value is changed.
+ return AppOpsManager.opRestrictsRead(op) && context.checkPermission(
+ Manifest.permission.MANAGE_APPOPS,
+ listener.callingPid,
+ listener.callingUid
+ ) != PackageManager.PERMISSION_GRANTED
+ }
+
+ /**
+ * Construct a map from each listener (listening to the given op, uid) to all of its associated
+ * packageNames (by reverse-indexing opModeWatchers and packageModeWatchers), then invoke
+ * notifyOpChanged for each listener.
+ */
override fun notifyOpChangedForAllPkgsInUid(
op: Int,
uid: Int,
onlyForeground: Boolean,
callbackToIgnore: OnOpModeChangedListener?
) {
- TODO("Not yet implemented")
+ val uidPackageNames = getPackagesForUid(uid)
+ val callbackSpecs = IndexedMap<OnOpModeChangedListener, IndexedSet<String>>()
+
+ fun associateListenerWithPackageNames(
+ listener: OnOpModeChangedListener,
+ packageNames: Array<String>
+ ) {
+ val listenerIsForeground =
+ listener.flags.hasBits(AppOpsManager.WATCH_FOREGROUND_CHANGES)
+ if (onlyForeground && !listenerIsForeground) {
+ return
+ }
+ val changedPackages = callbackSpecs.getOrPut(listener) { IndexedSet() }
+ changedPackages.addAll(packageNames)
+ }
+
+ synchronized(lock) {
+ // Collect all listeners from opModeWatchers and pckageModeWatchers
+ val listeners = opModeWatchers[op]
+ listeners?.forEachIndexed { _, listener ->
+ associateListenerWithPackageNames(listener, uidPackageNames)
+ }
+ uidPackageNames.forEachIndexed { _, uidPackageName ->
+ val packageListeners = packageModeWatchers[uidPackageName]
+ packageListeners?.forEachIndexed { _, listener ->
+ associateListenerWithPackageNames(listener, arrayOf(uidPackageName))
+ }
+ }
+ // Remove ignored listeners
+ if (callbackToIgnore != null) {
+ callbackSpecs.remove(callbackToIgnore)
+ }
+ }
+
+ // For each (listener, packageName) pair, invoke notifyOpChanged
+ callbackSpecs.forEachIndexed { _, listener, reportedPackageNames ->
+ reportedPackageNames.forEachIndexed { _, reportedPackageName ->
+ handler.sendMessage(
+ PooledLambda.obtainMessage(
+ AppOpService::notifyOpChanged, this, listener,
+ op, uid, reportedPackageName
+ )
+ )
+ }
+ }
+ }
+
+ private fun getPackagesForUid(uid: Int): Array<String> {
+ // Very early during boot the package manager is not yet or not yet fully started. At this
+ // time there are no packages yet.
+ return try {
+ AppGlobals.getPackageManager()?.getPackagesForUid(uid) ?: EmptyArray.STRING
+ } catch (e: RemoteException) {
+ EmptyArray.STRING
+ }
}
override fun evalForegroundUidOps(
uid: Int,
foregroundOps: SparseBooleanArray?
- ): SparseBooleanArray {
- TODO("Not yet implemented")
+ ): SparseBooleanArray? {
+ synchronized(lock) {
+ val uidModes = getUidModes(uid)
+ return evalForegroundOps(uidModes, foregroundOps)
+ }
}
override fun evalForegroundPackageOps(
packageName: String,
foregroundOps: SparseBooleanArray?,
- userId: Int
- ): SparseBooleanArray {
- TODO("Not yet implemented")
+ @UserIdInt userId: Int
+ ): SparseBooleanArray? {
+ synchronized(lock) {
+ val ops = service.getState { getPackageModes(packageName, userId) }
+ return evalForegroundOps(ops, foregroundOps)
+ }
+ }
+
+ private fun evalForegroundOps(
+ ops: IndexedMap<String, Int>?,
+ foregroundOps: SparseBooleanArray?
+ ): SparseBooleanArray? {
+ var foregroundOps = foregroundOps
+ ops?.forEachIndexed { _, opName, opMode ->
+ if (opMode == AppOpsManager.MODE_FOREGROUND) {
+ if (foregroundOps == null) {
+ foregroundOps = SparseBooleanArray()
+ }
+ evalForegroundWatchers(opName, foregroundOps!!)
+ }
+ }
+ return foregroundOps
+ }
+
+ private fun evalForegroundWatchers(opName: String, foregroundOps: SparseBooleanArray) {
+ val opCode = AppOpsManager.strOpToOp(opName)
+ val listeners = opModeWatchers[opCode]
+ val hasForegroundListeners = foregroundOps[opCode] || listeners?.anyIndexed { _, listener ->
+ listener.flags.hasBits(AppOpsManager.WATCH_FOREGROUND_CHANGES)
+ } ?: false
+ foregroundOps.put(opCode, hasForegroundListeners)
}
override fun dumpListeners(
@@ -139,10 +393,76 @@
dumpPackage: String?,
printWriter: PrintWriter
): Boolean {
- TODO("Not yet implemented")
+ var needSep = false
+ if (opModeWatchers.size() > 0) {
+ var printedHeader = false
+ opModeWatchers.forEachIndexed { _, op, modeChangedListenerSet ->
+ if (dumpOp >= 0 && dumpOp != op) {
+ return@forEachIndexed // continue
+ }
+ val opName = AppOpsManager.opToName(op)
+ var printedOpHeader = false
+ modeChangedListenerSet.forEachIndexed listenerLoop@ { listenerIndex, listener ->
+ with(printWriter) {
+ if (dumpPackage != null &&
+ dumpUid != UserHandle.getAppId(listener.watchingUid)) {
+ return@listenerLoop // continue
+ }
+ needSep = true
+ if (!printedHeader) {
+ println(" Op mode watchers:")
+ printedHeader = true
+ }
+ if (!printedOpHeader) {
+ print(" Op ")
+ print(opName)
+ println(":")
+ printedOpHeader = true
+ }
+ print(" #")
+ print(listenerIndex)
+ print(opName)
+ print(": ")
+ println(listener.toString())
+ }
+ }
+ }
+ }
+
+ if (packageModeWatchers.size > 0 && dumpOp < 0) {
+ var printedHeader = false
+ packageModeWatchers.forEachIndexed { _, packageName, listeners ->
+ with(printWriter) {
+ if (dumpPackage != null && dumpPackage != packageName) {
+ return@forEachIndexed // continue
+ }
+ needSep = true
+ if (!printedHeader) {
+ println(" Package mode watchers:")
+ printedHeader = true
+ }
+ print(" Pkg ")
+ print(packageName)
+ println(":")
+ listeners.forEachIndexed { listenerIndex, listener ->
+ print(" #")
+ print(listenerIndex)
+ print(": ")
+ println(listener.toString())
+ }
+ }
+ }
+ }
+ return needSep
}
companion object {
private val LOG_TAG = AppOpService::class.java.simpleName
+
+ // Constant meaning that any UID should be matched when dispatching callbacks
+ private const val UID_ANY = -2
+
+ // If watchedOpCode==ALL_OPS, notify for ops affected by the switch-op
+ private const val ALL_OPS = -2
}
}
diff --git a/services/permission/java/com/android/server/permission/access/appop/PackageAppOpPolicy.kt b/services/permission/java/com/android/server/permission/access/appop/PackageAppOpPolicy.kt
index 607e512..7d3578d 100644
--- a/services/permission/java/com/android/server/permission/access/appop/PackageAppOpPolicy.kt
+++ b/services/permission/java/com/android/server/permission/access/appop/PackageAppOpPolicy.kt
@@ -48,6 +48,10 @@
setAppOpMode(subject.packageName, subject.userId, `object`.appOpName, decision)
}
+ override fun GetStateScope.onStateMutated() {
+ onAppOpModeChangedListeners.forEachIndexed { _, it -> it.onStateMutated() }
+ }
+
override fun MutateStateScope.onPackageRemoved(packageName: String, appId: Int) {
newState.userStates.forEachIndexed { _, _, userState ->
userState.packageAppOpModes -= packageName
@@ -56,8 +60,17 @@
}
}
- fun MutateStateScope.removeAppOpModes(packageName: String, userId: Int): Boolean =
- newState.userStates[userId].packageAppOpModes.remove(packageName) != null
+ fun GetStateScope.getAppOpModes(packageName: String, userId: Int): IndexedMap<String, Int>? =
+ state.userStates[userId].packageAppOpModes[packageName]
+
+ fun MutateStateScope.removeAppOpModes(packageName: String, userId: Int): Boolean {
+ val userState = newState.userStates[userId]
+ val isChanged = userState.packageAppOpModes.remove(packageName) != null
+ if (isChanged) {
+ userState.requestWrite()
+ }
+ return isChanged
+ }
fun GetStateScope.getAppOpMode(packageName: String, userId: Int, appOpName: String): Int =
state.userStates[userId].packageAppOpModes[packageName]
@@ -104,13 +117,30 @@
}
}
- fun interface OnAppOpModeChangedListener {
- fun onAppOpModeChanged(
+ /**
+ * Listener for app op mode changes.
+ */
+ abstract class OnAppOpModeChangedListener {
+ /**
+ * Called when an app op mode change has been made to the upcoming new state.
+ *
+ * Implementations should keep this method fast to avoid stalling the locked state mutation,
+ * and only call external code after [onStateMutated] when the new state has actually become
+ * the current state visible to external code.
+ */
+ abstract fun onAppOpModeChanged(
packageName: String,
userId: Int,
appOpName: String,
oldMode: Int,
newMode: Int
)
+
+ /**
+ * Called when the upcoming new state has become the current state.
+ *
+ * Implementations should keep this method fast to avoid stalling the locked state mutation.
+ */
+ abstract fun onStateMutated()
}
}
diff --git a/services/permission/java/com/android/server/permission/access/appop/UidAppOpPolicy.kt b/services/permission/java/com/android/server/permission/access/appop/UidAppOpPolicy.kt
index 0b01038..0ba9a1e 100644
--- a/services/permission/java/com/android/server/permission/access/appop/UidAppOpPolicy.kt
+++ b/services/permission/java/com/android/server/permission/access/appop/UidAppOpPolicy.kt
@@ -48,6 +48,10 @@
setAppOpMode(subject.appId, subject.userId, `object`.appOpName, decision)
}
+ override fun GetStateScope.onStateMutated() {
+ onAppOpModeChangedListeners.forEachIndexed { _, it -> it.onStateMutated() }
+ }
+
override fun MutateStateScope.onAppIdRemoved(appId: Int) {
newState.userStates.forEachIndexed { _, _, userState ->
userState.uidAppOpModes -= appId
@@ -59,8 +63,14 @@
fun GetStateScope.getAppOpModes(appId: Int, userId: Int): IndexedMap<String, Int>? =
state.userStates[userId].uidAppOpModes[appId]
- fun MutateStateScope.removeAppOpModes(appId: Int, userId: Int): Boolean =
- newState.userStates[userId].uidAppOpModes.removeReturnOld(appId) != null
+ fun MutateStateScope.removeAppOpModes(appId: Int, userId: Int): Boolean {
+ val userState = newState.userStates[userId]
+ val isChanged = userState.uidAppOpModes.removeReturnOld(appId) != null
+ if (isChanged) {
+ userState.requestWrite()
+ }
+ return isChanged
+ }
fun GetStateScope.getAppOpMode(appId: Int, userId: Int, appOpName: String): Int =
state.userStates[userId].uidAppOpModes[appId]
@@ -107,13 +117,30 @@
}
}
- fun interface OnAppOpModeChangedListener {
- fun onAppOpModeChanged(
+ /**
+ * Listener for app op mode changes.
+ */
+ abstract class OnAppOpModeChangedListener {
+ /**
+ * Called when an app op mode change has been made to the upcoming new state.
+ *
+ * Implementations should keep this method fast to avoid stalling the locked state mutation,
+ * and only call external code after [onStateMutated] when the new state has actually become
+ * the current state visible to external code.
+ */
+ abstract fun onAppOpModeChanged(
appId: Int,
userId: Int,
appOpName: String,
oldMode: Int,
newMode: Int
)
+
+ /**
+ * Called when the upcoming new state has become the current state.
+ *
+ * Implementations should keep this method fast to avoid stalling the locked state mutation.
+ */
+ abstract fun onStateMutated()
}
}
diff --git a/services/permission/java/com/android/server/permission/access/collection/IndexedMap.kt b/services/permission/java/com/android/server/permission/access/collection/IndexedMap.kt
index 1e73be7..f4e362c 100644
--- a/services/permission/java/com/android/server/permission/access/collection/IndexedMap.kt
+++ b/services/permission/java/com/android/server/permission/access/collection/IndexedMap.kt
@@ -148,10 +148,21 @@
return isChanged
}
-inline fun <K, V, R> IndexedMap<K, V>.mapNotNullIndexed(transform: (K, V) -> R?): IndexedList<R> =
+inline fun <K, V, R> IndexedMap<K, V>.mapNotNullIndexed(
+ transform: (Int, K, V) -> R?
+): IndexedList<R> =
IndexedList<R>().also { destination ->
- forEachIndexed { _, key, value ->
- transform(key, value)?.let { destination += it }
+ forEachIndexed { index, key, value ->
+ transform(index, key, value)?.let { destination += it }
+ }
+ }
+
+inline fun <K, V, R> IndexedMap<K, V>.mapNotNullIndexedToSet(
+ transform: (Int, K, V) -> R?
+): IndexedSet<R> =
+ IndexedSet<R>().also { destination ->
+ forEachIndexed { index, key, value ->
+ transform(index, key, value)?.let { destination += it }
}
}
diff --git a/services/permission/java/com/android/server/permission/access/collection/IntBooleanMap.kt b/services/permission/java/com/android/server/permission/access/collection/IntBooleanMap.kt
new file mode 100644
index 0000000..2f7b9bf
--- /dev/null
+++ b/services/permission/java/com/android/server/permission/access/collection/IntBooleanMap.kt
@@ -0,0 +1,171 @@
+/*
+ * Copyright (C) 2022 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.permission.access.collection
+
+import android.util.SparseBooleanArray
+
+typealias IntBooleanMap = SparseBooleanArray
+
+inline fun IntBooleanMap.allIndexed(predicate: (Int, Int, Boolean) -> Boolean): Boolean {
+ forEachIndexed { index, key, value ->
+ if (!predicate(index, key, value)) {
+ return false
+ }
+ }
+ return true
+}
+
+inline fun IntBooleanMap.anyIndexed(predicate: (Int, Int, Boolean) -> Boolean): Boolean {
+ forEachIndexed { index, key, value ->
+ if (predicate(index, key, value)) {
+ return true
+ }
+ }
+ return false
+}
+
+@Suppress("NOTHING_TO_INLINE")
+inline fun IntBooleanMap.copy(): IntBooleanMap = clone()
+
+inline fun <R> IntBooleanMap.firstNotNullOfOrNullIndexed(transform: (Int, Int, Boolean) -> R): R? {
+ forEachIndexed { index, key, value ->
+ transform(index, key, value)?.let { return it }
+ }
+ return null
+}
+
+inline fun IntBooleanMap.forEachIndexed(action: (Int, Int, Boolean) -> Unit) {
+ for (index in 0 until size) {
+ action(index, keyAt(index), valueAt(index))
+ }
+}
+
+inline fun IntBooleanMap.forEachKeyIndexed(action: (Int, Int) -> Unit) {
+ for (index in 0 until size) {
+ action(index, keyAt(index))
+ }
+}
+
+inline fun IntBooleanMap.forEachReversedIndexed(action: (Int, Int, Boolean) -> Unit) {
+ for (index in lastIndex downTo 0) {
+ action(index, keyAt(index), valueAt(index))
+ }
+}
+
+inline fun IntBooleanMap.forEachValueIndexed(action: (Int, Boolean) -> Unit) {
+ for (index in 0 until size) {
+ action(index, valueAt(index))
+ }
+}
+
+inline fun IntBooleanMap.getOrPut(key: Int, defaultValue: () -> Boolean): Boolean {
+ val index = indexOfKey(key)
+ return if (index >= 0) {
+ valueAt(index)
+ } else {
+ defaultValue().also { put(key, it) }
+ }
+}
+
+@Suppress("NOTHING_TO_INLINE")
+inline fun IntBooleanMap?.getWithDefault(key: Int, defaultValue: Boolean): Boolean {
+ this ?: return defaultValue
+ return get(key, defaultValue)
+}
+
+inline val IntBooleanMap.lastIndex: Int
+ get() = size - 1
+
+@Suppress("NOTHING_TO_INLINE")
+inline operator fun IntBooleanMap.minusAssign(key: Int) {
+ delete(key)
+}
+
+inline fun IntBooleanMap.noneIndexed(predicate: (Int, Int, Boolean) -> Boolean): Boolean {
+ forEachIndexed { index, key, value ->
+ if (predicate(index, key, value)) {
+ return false
+ }
+ }
+ return true
+}
+
+@Suppress("NOTHING_TO_INLINE")
+inline fun IntBooleanMap.putWithDefault(key: Int, value: Boolean, defaultValue: Boolean): Boolean {
+ val index = indexOfKey(key)
+ if (index >= 0) {
+ val oldValue = valueAt(index)
+ if (value != oldValue) {
+ if (value == defaultValue) {
+ removeAt(index)
+ } else {
+ setValueAt(index, value)
+ }
+ }
+ return oldValue
+ } else {
+ if (value != defaultValue) {
+ put(key, value)
+ }
+ return defaultValue
+ }
+}
+
+fun IntBooleanMap.remove(key: Int) {
+ delete(key)
+}
+
+fun IntBooleanMap.remove(key: Int, defaultValue: Boolean): Boolean {
+ val index = indexOfKey(key)
+ return if (index >= 0) {
+ val oldValue = valueAt(index)
+ removeAt(index)
+ oldValue
+ } else {
+ defaultValue
+ }
+}
+
+inline fun IntBooleanMap.removeAllIndexed(predicate: (Int, Int, Boolean) -> Boolean): Boolean {
+ var isChanged = false
+ forEachReversedIndexed { index, key, value ->
+ if (predicate(index, key, value)) {
+ removeAt(index)
+ isChanged = true
+ }
+ }
+ return isChanged
+}
+
+inline fun IntBooleanMap.retainAllIndexed(predicate: (Int, Int, Boolean) -> Boolean): Boolean {
+ var isChanged = false
+ forEachReversedIndexed { index, key, value ->
+ if (!predicate(index, key, value)) {
+ removeAt(index)
+ isChanged = true
+ }
+ }
+ return isChanged
+}
+
+@Suppress("NOTHING_TO_INLINE")
+inline operator fun IntBooleanMap.set(key: Int, value: Boolean) {
+ put(key, value)
+}
+
+inline val IntBooleanMap.size: Int
+ get() = size()
diff --git a/services/permission/java/com/android/server/permission/access/permission/Permission.kt b/services/permission/java/com/android/server/permission/access/permission/Permission.kt
index de2df747..88989c4 100644
--- a/services/permission/java/com/android/server/permission/access/permission/Permission.kt
+++ b/services/permission/java/com/android/server/permission/access/permission/Permission.kt
@@ -17,13 +17,18 @@
package com.android.server.permission.access.permission
import android.content.pm.PermissionInfo
+import android.os.UserHandle
import com.android.server.permission.access.util.hasBits
+import libcore.util.EmptyArray
data class Permission(
val permissionInfo: PermissionInfo,
val isReconciled: Boolean,
val type: Int,
- val appId: Int
+ val appId: Int,
+ @Suppress("ArrayInDataClass")
+ val gids: IntArray = EmptyArray.INT,
+ val areGidsPerUser: Boolean = false
) {
inline val name: String
get() = permissionInfo.name
@@ -37,36 +42,50 @@
inline val isDynamic: Boolean
get() = type == TYPE_DYNAMIC
+ inline val protectionLevel: Int
+ @Suppress("DEPRECATION")
+ get() = permissionInfo.protectionLevel
+
+ inline val isInternal: Boolean
+ get() = permissionInfo.protection == PermissionInfo.PROTECTION_INTERNAL
+
inline val isNormal: Boolean
get() = permissionInfo.protection == PermissionInfo.PROTECTION_NORMAL
inline val isRuntime: Boolean
get() = permissionInfo.protection == PermissionInfo.PROTECTION_DANGEROUS
- inline val isAppOp: Boolean
- get() = permissionInfo.protection == PermissionInfo.PROTECTION_FLAG_APPOP
-
- inline val isRemoved: Boolean
- get() = permissionInfo.flags.hasBits(PermissionInfo.FLAG_REMOVED)
-
- inline val isSoftRestricted: Boolean
- get() = permissionInfo.flags.hasBits(PermissionInfo.FLAG_SOFT_RESTRICTED)
-
- inline val isHardRestricted: Boolean
- get() = permissionInfo.flags.hasBits(PermissionInfo.FLAG_HARD_RESTRICTED)
-
inline val isSignature: Boolean
get() = permissionInfo.protection == PermissionInfo.PROTECTION_SIGNATURE
- inline val isInternal: Boolean
- get() = permissionInfo.protection == PermissionInfo.PROTECTION_INTERNAL
+ inline val isAppOp: Boolean
+ get() = permissionInfo.protectionFlags.hasBits(PermissionInfo.PROTECTION_FLAG_APPOP)
+
+ inline val isAppPredictor: Boolean
+ get() = permissionInfo.protectionFlags.hasBits(PermissionInfo.PROTECTION_FLAG_APP_PREDICTOR)
+
+ inline val isCompanion: Boolean
+ get() = permissionInfo.protectionFlags.hasBits(PermissionInfo.PROTECTION_FLAG_COMPANION)
+
+ inline val isConfigurator: Boolean
+ get() = permissionInfo.protectionFlags.hasBits(PermissionInfo.PROTECTION_FLAG_CONFIGURATOR)
inline val isDevelopment: Boolean
get() = permissionInfo.protectionFlags.hasBits(PermissionInfo.PROTECTION_FLAG_DEVELOPMENT)
+ inline val isIncidentReportApprover: Boolean
+ get() = permissionInfo.protectionFlags
+ .hasBits(PermissionInfo.PROTECTION_FLAG_INCIDENT_REPORT_APPROVER)
+
inline val isInstaller: Boolean
get() = permissionInfo.protectionFlags.hasBits(PermissionInfo.PROTECTION_FLAG_INSTALLER)
+ inline val isInstant: Boolean
+ get() = permissionInfo.protectionFlags.hasBits(PermissionInfo.PROTECTION_FLAG_INSTANT)
+
+ inline val isKnownSigner: Boolean
+ get() = permissionInfo.protectionFlags.hasBits(PermissionInfo.PROTECTION_FLAG_KNOWN_SIGNER)
+
inline val isOem: Boolean
get() = permissionInfo.protectionFlags.hasBits(PermissionInfo.PROTECTION_FLAG_OEM)
@@ -79,55 +98,54 @@
inline val isPrivileged: Boolean
get() = permissionInfo.protectionFlags.hasBits(PermissionInfo.PROTECTION_FLAG_PRIVILEGED)
+ inline val isRecents: Boolean
+ get() = permissionInfo.protectionFlags.hasBits(PermissionInfo.PROTECTION_FLAG_RECENTS)
+
+ inline val isRetailDemo: Boolean
+ get() = permissionInfo.protectionFlags.hasBits(PermissionInfo.PROTECTION_FLAG_RETAIL_DEMO)
+
+ inline val isRole: Boolean
+ get() = permissionInfo.protectionFlags.hasBits(PermissionInfo.PROTECTION_FLAG_ROLE)
+
+ inline val isRuntimeOnly: Boolean
+ get() = permissionInfo.protectionFlags.hasBits(PermissionInfo.PROTECTION_FLAG_RUNTIME_ONLY)
+
inline val isSetup: Boolean
get() = permissionInfo.protectionFlags.hasBits(PermissionInfo.PROTECTION_FLAG_SETUP)
- inline val isVerifier: Boolean
- get() = permissionInfo.protectionFlags.hasBits(PermissionInfo.PROTECTION_FLAG_VERIFIER)
-
- inline val isVendorPrivileged: Boolean
- get() = permissionInfo.protectionFlags
- .hasBits(PROTECTION_FLAG_VENDOR_PRIVILEGED)
-
inline val isSystemTextClassifier: Boolean
get() = permissionInfo.protectionFlags
.hasBits(PermissionInfo.PROTECTION_FLAG_SYSTEM_TEXT_CLASSIFIER)
- inline val isConfigurator: Boolean
- get() = permissionInfo.protectionFlags.hasBits(PermissionInfo.PROTECTION_FLAG_CONFIGURATOR)
-
- inline val isIncidentReportApprover: Boolean
+ inline val isVendorPrivileged: Boolean
get() = permissionInfo.protectionFlags
- .hasBits(PermissionInfo.PROTECTION_FLAG_INCIDENT_REPORT_APPROVER)
+ .hasBits(PermissionInfo.PROTECTION_FLAG_VENDOR_PRIVILEGED)
- inline val isAppPredictor: Boolean
- get() = permissionInfo.protectionFlags.hasBits(PermissionInfo.PROTECTION_FLAG_APP_PREDICTOR)
+ inline val isVerifier: Boolean
+ get() = permissionInfo.protectionFlags.hasBits(PermissionInfo.PROTECTION_FLAG_VERIFIER)
- inline val isCompanion: Boolean
- get() = permissionInfo.protectionFlags.hasBits(PermissionInfo.PROTECTION_FLAG_COMPANION)
+ inline val isHardRestricted: Boolean
+ get() = permissionInfo.flags.hasBits(PermissionInfo.FLAG_HARD_RESTRICTED)
- inline val isRetailDemo: Boolean
- get() = permissionInfo.protectionFlags.hasBits(PermissionInfo.PROTECTION_FLAG_RETAIL_DEMO)
+ inline val isRemoved: Boolean
+ get() = permissionInfo.flags.hasBits(PermissionInfo.FLAG_REMOVED)
- inline val isRecents: Boolean
- get() = permissionInfo.protectionFlags.hasBits(PermissionInfo.PROTECTION_FLAG_RECENTS)
-
- inline val isRole: Boolean
- get() = permissionInfo.protectionFlags.hasBits(PermissionInfo.PROTECTION_FLAG_ROLE)
-
- inline val isKnownSigner: Boolean
- get() = permissionInfo.protectionFlags.hasBits(PermissionInfo.PROTECTION_FLAG_KNOWN_SIGNER)
-
- inline val hasGids: Boolean
- get() = throw NotImplementedError()
-
- inline val protectionLevel: Int
- @Suppress("DEPRECATION")
- get() = permissionInfo.protectionLevel
+ inline val isSoftRestricted: Boolean
+ get() = permissionInfo.flags.hasBits(PermissionInfo.FLAG_SOFT_RESTRICTED)
inline val knownCerts: Set<String>
get() = permissionInfo.knownCerts
+ inline val hasGids: Boolean
+ get() = gids.isNotEmpty()
+
+ fun getGidsForUser(userId: Int): IntArray =
+ if (areGidsPerUser) {
+ IntArray(gids.size) { i -> UserHandle.getUid(userId, gids[i]) }
+ } else {
+ gids.clone()
+ }
+
companion object {
// The permission is defined in an application manifest.
const val TYPE_MANIFEST = 0
@@ -135,8 +153,5 @@
const val TYPE_CONFIG = 1
// The permission is defined dynamically.
const val TYPE_DYNAMIC = 2
-
- // TODO: PermissionInfo.PROTECTION_FLAG_VENDOR_PRIVILEGED is a testApi
- const val PROTECTION_FLAG_VENDOR_PRIVILEGED = 0x8000
}
}
diff --git a/services/permission/java/com/android/server/permission/access/permission/PermissionFlags.kt b/services/permission/java/com/android/server/permission/access/permission/PermissionFlags.kt
index a4708c8..48658ff 100644
--- a/services/permission/java/com/android/server/permission/access/permission/PermissionFlags.kt
+++ b/services/permission/java/com/android/server/permission/access/permission/PermissionFlags.kt
@@ -317,172 +317,164 @@
*/
const val MASK_EXEMPT = INSTALLER_EXEMPT or SYSTEM_EXEMPT or UPGRADE_EXEMPT
- /**
- * Mask for all API permission flags about permission restriction.
- */
- private const val API_MASK_RESTRICTION =
- PackageManager.FLAG_PERMISSION_RESTRICTION_INSTALLER_EXEMPT or
- PackageManager.FLAG_PERMISSION_RESTRICTION_SYSTEM_EXEMPT or
- PackageManager.FLAG_PERMISSION_RESTRICTION_UPGRADE_EXEMPT or
- PackageManager.FLAG_PERMISSION_APPLY_RESTRICTION
-
- /**
- * Mask for all permission flags about permission restriction.
- */
- private const val MASK_RESTRICTION = INSTALLER_EXEMPT or SYSTEM_EXEMPT or
- UPGRADE_EXEMPT or RESTRICTION_REVOKED or SOFT_RESTRICTED
-
- fun isPermissionGranted(policyFlags: Int): Boolean {
- if (policyFlags.hasBits(INSTALL_GRANTED)) {
+ fun isPermissionGranted(flags: Int): Boolean {
+ if (flags.hasBits(INSTALL_GRANTED)) {
return true
}
- if (policyFlags.hasBits(INSTALL_REVOKED)) {
+ if (flags.hasBits(INSTALL_REVOKED)) {
return false
}
- if (policyFlags.hasBits(PROTECTION_GRANTED)) {
+ if (flags.hasBits(PROTECTION_GRANTED)) {
return true
}
- if (policyFlags.hasBits(LEGACY_GRANTED) || policyFlags.hasBits(IMPLICIT_GRANTED)) {
+ if (flags.hasBits(LEGACY_GRANTED) || flags.hasBits(IMPLICIT_GRANTED)) {
return true
}
- if (policyFlags.hasBits(RESTRICTION_REVOKED)) {
+ if (flags.hasBits(RESTRICTION_REVOKED)) {
return false
}
- return policyFlags.hasBits(RUNTIME_GRANTED)
+ return flags.hasBits(RUNTIME_GRANTED)
}
- fun isAppOpGranted(policyFlags: Int): Boolean =
- isPermissionGranted(policyFlags) && !policyFlags.hasBits(APP_OP_REVOKED)
+ fun isAppOpGranted(flags: Int): Boolean =
+ isPermissionGranted(flags) && !flags.hasBits(APP_OP_REVOKED)
- fun isReviewRequired(policyFlags: Int): Boolean =
- policyFlags.hasBits(LEGACY_GRANTED) && policyFlags.hasBits(IMPLICIT)
-
- fun toApiFlags(policyFlags: Int): Int {
+ fun toApiFlags(flags: Int): Int {
var apiFlags = 0
- if (policyFlags.hasBits(USER_SET)) {
+ if (flags.hasBits(USER_SET)) {
apiFlags = apiFlags or PackageManager.FLAG_PERMISSION_USER_SET
}
- if (policyFlags.hasBits(USER_FIXED)) {
+ if (flags.hasBits(USER_FIXED)) {
apiFlags = apiFlags or PackageManager.FLAG_PERMISSION_USER_FIXED
}
- if (policyFlags.hasBits(POLICY_FIXED)) {
+ if (flags.hasBits(POLICY_FIXED)) {
apiFlags = apiFlags or PackageManager.FLAG_PERMISSION_POLICY_FIXED
}
- if (policyFlags.hasBits(SYSTEM_FIXED)) {
+ if (flags.hasBits(SYSTEM_FIXED)) {
apiFlags = apiFlags or PackageManager.FLAG_PERMISSION_SYSTEM_FIXED
}
- if (policyFlags.hasBits(PREGRANT)) {
+ if (flags.hasBits(PREGRANT)) {
apiFlags = apiFlags or PackageManager.FLAG_PERMISSION_GRANTED_BY_DEFAULT
}
- if (policyFlags.hasBits(IMPLICIT)) {
- apiFlags = apiFlags or if (policyFlags.hasBits(LEGACY_GRANTED)) {
+ if (flags.hasBits(IMPLICIT)) {
+ apiFlags = apiFlags or if (flags.hasBits(LEGACY_GRANTED)) {
PackageManager.FLAG_PERMISSION_REVIEW_REQUIRED
} else {
PackageManager.FLAG_PERMISSION_REVOKE_WHEN_REQUESTED
}
}
- if (policyFlags.hasBits(USER_SENSITIVE_WHEN_GRANTED)) {
+ if (flags.hasBits(USER_SENSITIVE_WHEN_GRANTED)) {
apiFlags = apiFlags or PackageManager.FLAG_PERMISSION_USER_SENSITIVE_WHEN_GRANTED
}
- if (policyFlags.hasBits(USER_SENSITIVE_WHEN_REVOKED)) {
+ if (flags.hasBits(USER_SENSITIVE_WHEN_REVOKED)) {
apiFlags = apiFlags or PackageManager.FLAG_PERMISSION_USER_SENSITIVE_WHEN_DENIED
}
- if (policyFlags.hasBits(INSTALLER_EXEMPT)) {
+ if (flags.hasBits(INSTALLER_EXEMPT)) {
apiFlags = apiFlags or PackageManager.FLAG_PERMISSION_RESTRICTION_INSTALLER_EXEMPT
}
- if (policyFlags.hasBits(SYSTEM_EXEMPT)) {
+ if (flags.hasBits(SYSTEM_EXEMPT)) {
apiFlags = apiFlags or PackageManager.FLAG_PERMISSION_RESTRICTION_SYSTEM_EXEMPT
}
- if (policyFlags.hasBits(UPGRADE_EXEMPT)) {
+ if (flags.hasBits(UPGRADE_EXEMPT)) {
apiFlags = apiFlags or PackageManager.FLAG_PERMISSION_RESTRICTION_UPGRADE_EXEMPT
}
- if (policyFlags.hasBits(RESTRICTION_REVOKED) || policyFlags.hasBits(SOFT_RESTRICTED)) {
+ if (flags.hasBits(RESTRICTION_REVOKED) || flags.hasBits(SOFT_RESTRICTED)) {
apiFlags = apiFlags or PackageManager.FLAG_PERMISSION_APPLY_RESTRICTION
}
- if (policyFlags.hasBits(ROLE)) {
+ if (flags.hasBits(ROLE)) {
apiFlags = apiFlags or PackageManager.FLAG_PERMISSION_GRANTED_BY_ROLE
}
- if (policyFlags.hasBits(APP_OP_REVOKED)) {
+ if (flags.hasBits(APP_OP_REVOKED)) {
apiFlags = apiFlags or PackageManager.FLAG_PERMISSION_REVOKED_COMPAT
}
- if (policyFlags.hasBits(ONE_TIME)) {
+ if (flags.hasBits(ONE_TIME)) {
apiFlags = apiFlags or PackageManager.FLAG_PERMISSION_ONE_TIME
}
- if (policyFlags.hasBits(HIBERNATION)) {
+ if (flags.hasBits(HIBERNATION)) {
apiFlags = apiFlags or PackageManager.FLAG_PERMISSION_AUTO_REVOKED
}
- if (policyFlags.hasBits(USER_SELECTED)) {
+ if (flags.hasBits(USER_SELECTED)) {
apiFlags = apiFlags or PackageManager.FLAG_PERMISSION_SELECTED_LOCATION_ACCURACY
}
return apiFlags
}
- fun setRuntimePermissionGranted(policyFlags: Int, isGranted: Boolean): Int =
- if (isGranted) policyFlags or RUNTIME_GRANTED else policyFlags andInv RUNTIME_GRANTED
+ fun updateRuntimePermissionGranted(flags: Int, isGranted: Boolean): Int =
+ if (isGranted) flags or RUNTIME_GRANTED else flags andInv RUNTIME_GRANTED
- fun updatePolicyFlags(policyFlags: Int, apiFlagMask: Int, apiFlagValues: Int): Int {
- check(!apiFlagMask.hasAnyBit(API_MASK_RESTRICTION)) {
- "Permission flags about permission restriction can only be directly mutated by the" +
- " policy"
- }
- val oldApiFlags = toApiFlags(policyFlags)
+ fun updateFlags(permission: Permission, flags: Int, apiFlagMask: Int, apiFlagValues: Int): Int {
+ val oldApiFlags = toApiFlags(flags)
val newApiFlags = (oldApiFlags andInv apiFlagMask) or (apiFlagValues and apiFlagMask)
- return toPolicyFlags(policyFlags, newApiFlags)
+ return fromApiFlags(newApiFlags, permission, flags)
}
- private fun toPolicyFlags(oldPolicyFlags: Int, apiFlags: Int): Int {
- var policyFlags = 0
- policyFlags = policyFlags or (oldPolicyFlags and INSTALL_GRANTED)
- policyFlags = policyFlags or (oldPolicyFlags and INSTALL_REVOKED)
- policyFlags = policyFlags or (oldPolicyFlags and PROTECTION_GRANTED)
+ private fun fromApiFlags(apiFlags: Int, permission: Permission, oldFlags: Int): Int {
+ var flags = 0
+ flags = flags or (oldFlags and INSTALL_GRANTED)
+ flags = flags or (oldFlags and INSTALL_REVOKED)
+ flags = flags or (oldFlags and PROTECTION_GRANTED)
if (apiFlags.hasBits(PackageManager.FLAG_PERMISSION_GRANTED_BY_ROLE)) {
- policyFlags = policyFlags or ROLE
+ flags = flags or ROLE
}
- policyFlags = policyFlags or (oldPolicyFlags and RUNTIME_GRANTED)
+ flags = flags or (oldFlags and RUNTIME_GRANTED)
if (apiFlags.hasBits(PackageManager.FLAG_PERMISSION_USER_SET)) {
- policyFlags = policyFlags or USER_SET
+ flags = flags or USER_SET
}
if (apiFlags.hasBits(PackageManager.FLAG_PERMISSION_USER_FIXED)) {
- policyFlags = policyFlags or USER_FIXED
+ flags = flags or USER_FIXED
}
if (apiFlags.hasBits(PackageManager.FLAG_PERMISSION_POLICY_FIXED)) {
- policyFlags = policyFlags or POLICY_FIXED
+ flags = flags or POLICY_FIXED
}
if (apiFlags.hasBits(PackageManager.FLAG_PERMISSION_SYSTEM_FIXED)) {
- policyFlags = policyFlags or SYSTEM_FIXED
+ flags = flags or SYSTEM_FIXED
}
if (apiFlags.hasBits(PackageManager.FLAG_PERMISSION_GRANTED_BY_DEFAULT)) {
- policyFlags = policyFlags or PREGRANT
+ flags = flags or PREGRANT
}
- policyFlags = policyFlags or (oldPolicyFlags and LEGACY_GRANTED)
- policyFlags = policyFlags or (oldPolicyFlags and IMPLICIT_GRANTED)
+ flags = flags or (oldFlags and LEGACY_GRANTED)
+ flags = flags or (oldFlags and IMPLICIT_GRANTED)
if (apiFlags.hasBits(PackageManager.FLAG_PERMISSION_REVIEW_REQUIRED) ||
apiFlags.hasBits(PackageManager.FLAG_PERMISSION_REVOKE_WHEN_REQUESTED)) {
- policyFlags = policyFlags or IMPLICIT
+ flags = flags or IMPLICIT
}
if (apiFlags.hasBits(PackageManager.FLAG_PERMISSION_USER_SENSITIVE_WHEN_GRANTED)) {
- policyFlags = policyFlags or USER_SENSITIVE_WHEN_GRANTED
+ flags = flags or USER_SENSITIVE_WHEN_GRANTED
}
if (apiFlags.hasBits(PackageManager.FLAG_PERMISSION_USER_SENSITIVE_WHEN_DENIED)) {
- policyFlags = policyFlags or USER_SENSITIVE_WHEN_REVOKED
+ flags = flags or USER_SENSITIVE_WHEN_REVOKED
}
- // FLAG_PERMISSION_APPLY_RESTRICTION can be either REVOKED_BY_RESTRICTION when the
- // permission is hard restricted, or SOFT_RESTRICTED when the permission is soft restricted.
- // However since we should never allow indirect mutation of restriction state, we can just
- // get the flags about restriction from the old policy flags.
- policyFlags = policyFlags or (oldPolicyFlags and MASK_RESTRICTION)
+ if (apiFlags.hasBits(PackageManager.FLAG_PERMISSION_RESTRICTION_INSTALLER_EXEMPT)) {
+ flags = flags or INSTALLER_EXEMPT
+ }
+ if (apiFlags.hasBits(PackageManager.FLAG_PERMISSION_RESTRICTION_SYSTEM_EXEMPT)) {
+ flags = flags or SYSTEM_EXEMPT
+ }
+ if (apiFlags.hasBits(PackageManager.FLAG_PERMISSION_RESTRICTION_UPGRADE_EXEMPT)) {
+ flags = flags or UPGRADE_EXEMPT
+ }
+ // We ignore whether FLAG_PERMISSION_APPLY_RESTRICTION is set here because previously
+ // platform may be relying on the old restorePermissionState() to get it correct later.
+ if (!flags.hasAnyBit(MASK_EXEMPT)) {
+ if (permission.isHardRestricted) {
+ flags = flags or RESTRICTION_REVOKED
+ }
+ if (permission.isSoftRestricted) {
+ flags = flags or SOFT_RESTRICTED
+ }
+ }
if (apiFlags.hasBits(PackageManager.FLAG_PERMISSION_REVOKED_COMPAT)) {
- policyFlags = policyFlags or APP_OP_REVOKED
+ flags = flags or APP_OP_REVOKED
}
if (apiFlags.hasBits(PackageManager.FLAG_PERMISSION_ONE_TIME)) {
- policyFlags = policyFlags or ONE_TIME
+ flags = flags or ONE_TIME
}
if (apiFlags.hasBits(PackageManager.FLAG_PERMISSION_AUTO_REVOKED)) {
- policyFlags = policyFlags or HIBERNATION
+ flags = flags or HIBERNATION
}
if (apiFlags.hasBits(PackageManager.FLAG_PERMISSION_SELECTED_LOCATION_ACCURACY)) {
- policyFlags = policyFlags or USER_SELECTED
+ flags = flags or USER_SELECTED
}
- return policyFlags
+ return flags
}
}
diff --git a/services/permission/java/com/android/server/permission/access/permission/PermissionService.kt b/services/permission/java/com/android/server/permission/access/permission/PermissionService.kt
index a70468e..7117501 100644
--- a/services/permission/java/com/android/server/permission/access/permission/PermissionService.kt
+++ b/services/permission/java/com/android/server/permission/access/permission/PermissionService.kt
@@ -26,6 +26,7 @@
import android.content.pm.PermissionGroupInfo
import android.content.pm.PermissionInfo
import android.content.pm.permission.SplitPermissionInfoParcelable
+import android.metrics.LogMaker
import android.os.Binder
import android.os.Build
import android.os.Handler
@@ -37,30 +38,46 @@
import android.os.RemoteException
import android.os.ServiceManager
import android.os.UserHandle
+import android.os.UserManager
import android.permission.IOnPermissionsChangeListener
import android.permission.PermissionManager
import android.provider.Settings
+import android.util.DebugUtils
+import android.util.IntArray as GrowingIntArray
import android.util.Log
import com.android.internal.compat.IPlatformCompat
+import com.android.internal.logging.MetricsLogger
+import com.android.internal.logging.nano.MetricsProto
+import com.android.internal.util.Preconditions
import com.android.server.FgThread
import com.android.server.LocalManagerRegistry
import com.android.server.LocalServices
import com.android.server.ServiceThread
import com.android.server.SystemConfig
-import com.android.server.pm.PackageManagerLocal
-import com.android.server.pm.permission.PermissionManagerServiceInterface
import com.android.server.permission.access.AccessCheckingService
+import com.android.server.permission.access.GetStateScope
+import com.android.server.permission.access.MutateStateScope
import com.android.server.permission.access.PermissionUri
import com.android.server.permission.access.UidUri
import com.android.server.permission.access.collection.* // ktlint-disable no-wildcard-imports
+import com.android.server.permission.access.util.andInv
import com.android.server.permission.access.util.hasAnyBit
import com.android.server.permission.access.util.hasBits
+import com.android.server.permission.access.util.withClearedCallingIdentity
+import com.android.server.pm.KnownPackages
+import com.android.server.pm.PackageManagerLocal
+import com.android.server.pm.UserManagerInternal
import com.android.server.pm.UserManagerService
+import com.android.server.pm.parsing.pkg.AndroidPackageUtils
import com.android.server.pm.permission.LegacyPermission
import com.android.server.pm.permission.LegacyPermissionSettings
import com.android.server.pm.permission.LegacyPermissionState
+import com.android.server.pm.permission.PermissionManagerServiceInterface
import com.android.server.pm.permission.PermissionManagerServiceInternal
import com.android.server.pm.pkg.AndroidPackage
+import com.android.server.pm.pkg.PackageState
+import com.android.server.policy.SoftRestrictedPermissionPolicy
+import libcore.util.EmptyArray
import java.io.FileDescriptor
import java.io.PrintWriter
@@ -74,39 +91,43 @@
service.getSchemePolicy(UidUri.SCHEME, PermissionUri.SCHEME) as UidPermissionPolicy
private val context = service.context
+ private lateinit var metricsLogger: MetricsLogger
private lateinit var packageManagerInternal: PackageManagerInternal
private lateinit var packageManagerLocal: PackageManagerLocal
private lateinit var platformCompat: IPlatformCompat
+ private lateinit var systemConfig: SystemConfig
+ private lateinit var userManagerInternal: UserManagerInternal
private lateinit var userManagerService: UserManagerService
- private val mountedStorageVolumes = IndexedSet<String?>()
-
private lateinit var handlerThread: HandlerThread
private lateinit var handler: Handler
-
private lateinit var onPermissionsChangeListeners: OnPermissionsChangeListeners
- private lateinit var permissionFlagsListener: OnPermissionFlagsChangedListener
+ private lateinit var onPermissionFlagsChangedListener: OnPermissionFlagsChangedListener
+
+ private val mountedStorageVolumes = IndexedSet<String?>()
fun initialize() {
+ metricsLogger = MetricsLogger()
packageManagerInternal = LocalServices.getService(PackageManagerInternal::class.java)
packageManagerLocal =
LocalManagerRegistry.getManagerOrThrow(PackageManagerLocal::class.java)
platformCompat = IPlatformCompat.Stub.asInterface(
ServiceManager.getService(Context.PLATFORM_COMPAT_SERVICE)
)
+ systemConfig = SystemConfig.getInstance()
+ userManagerInternal = LocalServices.getService(UserManagerInternal::class.java)
userManagerService = UserManagerService.getInstance()
handlerThread = ServiceThread(LOG_TAG, Process.THREAD_PRIORITY_BACKGROUND, true)
handler = Handler(handlerThread.looper)
-
onPermissionsChangeListeners = OnPermissionsChangeListeners(FgThread.get().looper)
- permissionFlagsListener = OnPermissionFlagsChangedListener()
- policy.addOnPermissionFlagsChangedListener(permissionFlagsListener)
+ onPermissionFlagsChangedListener = OnPermissionFlagsChangedListener()
+ policy.addOnPermissionFlagsChangedListener(onPermissionFlagsChangedListener)
}
override fun getAllPermissionGroups(flags: Int): List<PermissionGroupInfo> {
- val callingUid = Binder.getCallingUid()
packageManagerLocal.withUnfilteredSnapshot().use { snapshot ->
+ val callingUid = Binder.getCallingUid()
if (snapshot.isUidInstantApp(callingUid)) {
return emptyList()
}
@@ -115,7 +136,7 @@
with(policy) { getPermissionGroups() }
}
- return permissionGroups.mapNotNullIndexed { _, permissionGroup ->
+ return permissionGroups.mapNotNullIndexed { _, _, permissionGroup ->
if (snapshot.isPackageVisibleToUid(permissionGroup.packageName, callingUid)) {
permissionGroup.generatePermissionGroupInfo(flags)
} else {
@@ -184,12 +205,10 @@
return null
}
- val callingAppId = UserHandle.getAppId(callingUid)
- val opPackage = snapshot.packageStates[opPackageName]?.androidPackage
+ val opPackage = snapshot.getPackageState(opPackageName)?.androidPackage
targetSdkVersion = when {
// System sees all flags.
- callingAppId == Process.ROOT_UID || callingAppId == Process.SYSTEM_UID ||
- callingAppId == Process.SHELL_UID -> Build.VERSION_CODES.CUR_DEVELOPMENT
+ isRootOrSystemOrShell(callingUid) -> Build.VERSION_CODES.CUR_DEVELOPMENT
opPackage != null -> opPackage.targetSdkVersion
else -> Build.VERSION_CODES.CUR_DEVELOPMENT
}
@@ -223,31 +242,30 @@
permissionGroupName: String?,
flags: Int
): List<PermissionInfo>? {
- val callingUid = Binder.getCallingUid()
packageManagerLocal.withUnfilteredSnapshot().use { snapshot ->
+ val callingUid = Binder.getCallingUid()
if (snapshot.isUidInstantApp(callingUid)) {
return null
}
- if (permissionGroupName != null) {
- val permissionGroup = service.getState {
- with(policy) { getPermissionGroups()[permissionGroupName] }
- } ?: return null
+ val permissions: IndexedMap<String, Permission>
+ service.getState {
+ if (permissionGroupName != null) {
+ val permissionGroup =
+ with(policy) { getPermissionGroups()[permissionGroupName] } ?: return null
- if (!snapshot.isPackageVisibleToUid(
- permissionGroup.packageName, callingUid
- )) {
- return null
+ if (!snapshot.isPackageVisibleToUid(permissionGroup.packageName, callingUid)) {
+ return null
+ }
}
+
+ permissions = with(policy) { getPermissions() }
}
- val permissions = service.getState {
- with(policy) { getPermissions() }
- }
-
- return permissions.mapNotNullIndexed { _, permission ->
+ return permissions.mapNotNullIndexed { _, _, permission ->
if (permission.groupName == permissionGroupName &&
- snapshot.isPackageVisibleToUid(permission.packageName, callingUid)) {
+ snapshot.isPackageVisibleToUid(permission.packageName, callingUid)
+ ) {
permission.generatePermissionInfo(flags)
} else {
null
@@ -265,7 +283,10 @@
}
override fun getPermissionGids(permissionName: String, userId: Int): IntArray {
- TODO("Not yet implemented")
+ val permission = service.getState {
+ with(policy) { getPermissions()[permissionName] }
+ } ?: return EmptyArray.INT
+ return permission.getGidsForUser(userId)
}
override fun addPermission(permissionInfo: PermissionInfo, async: Boolean): Boolean {
@@ -276,20 +297,188 @@
TODO("Not yet implemented")
}
- override fun checkPermission(packageName: String, permissionName: String, userId: Int): Int {
- TODO("Not yet implemented")
+ override fun checkUidPermission(uid: Int, permissionName: String): Int {
+ val userId = UserHandle.getUserId(uid)
+ if (!userManagerInternal.exists(userId)) {
+ return PackageManager.PERMISSION_DENIED
+ }
+
+ // PackageManagerInternal.getPackage(int) already checks package visibility and enforces
+ // that instant apps can't see shared UIDs. Note that on the contrary,
+ // Note that PackageManagerInternal.getPackage(String) doesn't perform any checks.
+ val androidPackage = packageManagerInternal.getPackage(uid)
+ if (androidPackage != null) {
+ // Note that PackageManagerInternal.getPackageStateInternal() is not filtered.
+ val packageState =
+ packageManagerInternal.getPackageStateInternal(androidPackage.packageName)
+ if (packageState == null) {
+ Log.e(
+ LOG_TAG, "checkUidPermission: PackageState not found for AndroidPackage" +
+ " $androidPackage"
+ )
+ return PackageManager.PERMISSION_DENIED
+ }
+ val isPermissionGranted = service.getState {
+ isPermissionGranted(packageState, userId, permissionName)
+ }
+ return if (isPermissionGranted) {
+ PackageManager.PERMISSION_GRANTED
+ } else {
+ PackageManager.PERMISSION_DENIED
+ }
+ }
+
+ return if (isSystemUidPermissionGranted(uid, permissionName)) {
+ PackageManager.PERMISSION_GRANTED
+ } else {
+ PackageManager.PERMISSION_DENIED
+ }
}
- override fun checkUidPermission(uid: Int, permissionName: String): Int {
- TODO("Not yet implemented")
+ /**
+ * Internal implementation that should only be called by [checkUidPermission].
+ */
+ private fun isSystemUidPermissionGranted(uid: Int, permissionName: String): Boolean {
+ val uidPermissions = systemConfig.systemPermissions[uid] ?: return false
+ if (permissionName in uidPermissions) {
+ return true
+ }
+
+ val fullerPermissionName = FULLER_PERMISSIONS[permissionName]
+ if (fullerPermissionName != null && fullerPermissionName in uidPermissions) {
+ return true
+ }
+
+ return false
+ }
+
+ override fun checkPermission(packageName: String, permissionName: String, userId: Int): Int {
+ if (!userManagerInternal.exists(userId)) {
+ return PackageManager.PERMISSION_DENIED
+ }
+
+ val packageState = packageManagerLocal.withFilteredSnapshot(Binder.getCallingUid(), userId)
+ .use { it.getPackageState(packageName) } ?: return PackageManager.PERMISSION_DENIED
+
+ val isPermissionGranted = service.getState {
+ isPermissionGranted(packageState, userId, permissionName)
+ }
+ return if (isPermissionGranted) {
+ PackageManager.PERMISSION_GRANTED
+ } else {
+ PackageManager.PERMISSION_DENIED
+ }
+ }
+
+ /**
+ * Check whether a permission is granted, without any validation on caller.
+ *
+ * This method should always be called for checking whether a permission is granted, instead of
+ * reading permission flags directly from the policy.
+ */
+ private fun GetStateScope.isPermissionGranted(
+ packageState: PackageState,
+ userId: Int,
+ permissionName: String
+ ): Boolean {
+ val appId = packageState.appId
+ // Note that instant apps can't have shared UIDs, so we only need to check the current
+ // package state.
+ val isInstantApp = packageState.getUserStateOrDefault(userId).isInstantApp
+ if (isSinglePermissionGranted(appId, userId, isInstantApp, permissionName)) {
+ return true
+ }
+
+ val fullerPermissionName = FULLER_PERMISSIONS[permissionName]
+ if (fullerPermissionName != null &&
+ isSinglePermissionGranted(appId, userId, isInstantApp, fullerPermissionName)) {
+ return true
+ }
+
+ return false
+ }
+
+ /**
+ * Internal implementation that should only be called by [isPermissionGranted].
+ */
+ private fun GetStateScope.isSinglePermissionGranted(
+ appId: Int,
+ userId: Int,
+ isInstantApp: Boolean,
+ permissionName: String
+ ): Boolean {
+ val flags = with(policy) { getPermissionFlags(appId, userId, permissionName) }
+ if (!PermissionFlags.isPermissionGranted(flags)) {
+ return false
+ }
+
+ if (isInstantApp) {
+ val permission = with(policy) { getPermissions()[permissionName] } ?: return false
+ if (!permission.isInstant) {
+ return false
+ }
+ }
+
+ return true
}
override fun getGrantedPermissions(packageName: String, userId: Int): Set<String> {
- TODO("Not yet implemented")
+ requireNotNull(packageName) { "packageName cannot be null" }
+ Preconditions.checkArgumentNonnegative(userId, "userId")
+
+ val packageState = packageManagerLocal.withUnfilteredSnapshot()
+ .use { it.getPackageState(packageName) }
+ if (packageState == null) {
+ Log.w(LOG_TAG, "getGrantedPermissions: Unknown package $packageName")
+ return emptySet()
+ }
+
+ service.getState {
+ val permissionFlags = with(policy) { getUidPermissionFlags(packageState.appId, userId) }
+ ?: return emptySet()
+
+ return permissionFlags.mapNotNullIndexedToSet { _, permissionName, _ ->
+ if (isPermissionGranted(packageState, userId, permissionName)) {
+ permissionName
+ } else {
+ null
+ }
+ }
+ }
+ }
+
+ override fun getGidsForUid(uid: Int): IntArray {
+ val appId = UserHandle.getAppId(uid)
+ val userId = UserHandle.getUserId(uid)
+ val globalGids = systemConfig.globalGids
+ service.getState {
+ // Different from the old implementation, which returns an empty array when the
+ // permission state is not found, now we always return at least global GIDs. This is
+ // more consistent with the pre-S-refactor behavior. This is also because we are now
+ // actively trimming the per-UID objects when empty.
+ val permissionFlags = with(policy) { getUidPermissionFlags(appId, userId) }
+ ?: return globalGids.clone()
+
+ val gids = GrowingIntArray.wrap(globalGids)
+ permissionFlags.forEachIndexed { _, permissionName, flags ->
+ if (!PermissionFlags.isPermissionGranted(flags)) {
+ return@forEachIndexed
+ }
+
+ val permission = with(policy) { getPermissions()[permissionName] }
+ ?: return@forEachIndexed
+ val permissionGids = permission.getGidsForUser(userId)
+ if (permissionGids.isEmpty()) {
+ return@forEachIndexed
+ }
+ gids.addAll(permissionGids)
+ }
+ return gids.toArray()
+ }
}
override fun grantRuntimePermission(packageName: String, permissionName: String, userId: Int) {
- TODO("Not yet implemented")
+ setRuntimePermissionGranted(packageName, userId, permissionName, isGranted = true)
}
override fun revokeRuntimePermission(
@@ -298,31 +487,301 @@
userId: Int,
reason: String?
) {
- TODO("Not yet implemented")
+ setRuntimePermissionGranted(
+ packageName, userId, permissionName, isGranted = false, revokeReason = reason
+ )
}
override fun revokePostNotificationPermissionWithoutKillForTest(
packageName: String,
userId: Int
) {
- TODO("Not yet implemented")
+ setRuntimePermissionGranted(
+ packageName, userId, Manifest.permission.POST_NOTIFICATIONS, isGranted = false,
+ skipKillUid = true
+ )
}
- override fun addOnPermissionsChangeListener(listener: IOnPermissionsChangeListener) {
- onPermissionsChangeListeners.addListener(listener)
+ /**
+ * Shared internal implementation that should only be called by [grantRuntimePermission],
+ * [revokeRuntimePermission] and [revokePostNotificationPermissionWithoutKillForTest].
+ */
+ private fun setRuntimePermissionGranted(
+ packageName: String,
+ userId: Int,
+ permissionName: String,
+ isGranted: Boolean,
+ skipKillUid: Boolean = false,
+ revokeReason: String? = null
+ ) {
+ val methodName = if (isGranted) "grantRuntimePermission" else "revokeRuntimePermission"
+ val callingUid = Binder.getCallingUid()
+ val isDebugEnabled = if (isGranted) {
+ PermissionManager.DEBUG_TRACE_GRANTS
+ } else {
+ PermissionManager.DEBUG_TRACE_PERMISSION_UPDATES
+ }
+ if (isDebugEnabled &&
+ PermissionManager.shouldTraceGrant(packageName, permissionName, userId)) {
+ val callingUidName = packageManagerInternal.getNameForUid(callingUid)
+ Log.i(
+ LOG_TAG, "$methodName(packageName = $packageName," +
+ " permissionName = $permissionName" +
+ (if (isGranted) "" else "skipKillUid = $skipKillUid, reason = $revokeReason") +
+ ", userId = $userId," + " callingUid = $callingUidName ($callingUid))",
+ RuntimeException()
+ )
+ }
+
+ enforceCallingOrSelfCrossUserPermission(
+ userId, enforceFullPermission = true, enforceShellRestriction = true, methodName
+ )
+ val enforcedPermissionName = if (isGranted) {
+ Manifest.permission.GRANT_RUNTIME_PERMISSIONS
+ } else {
+ Manifest.permission.REVOKE_RUNTIME_PERMISSIONS
+ }
+ context.enforceCallingOrSelfPermission(enforcedPermissionName, methodName)
+
+ if (!userManagerInternal.exists(userId)) {
+ Log.w(LOG_TAG, "$methodName: Unknown user $userId")
+ return
+ }
+
+ val packageState: PackageState?
+ val permissionControllerPackageName = packageManagerInternal.getKnownPackageNames(
+ KnownPackages.PACKAGE_PERMISSION_CONTROLLER, UserHandle.USER_SYSTEM
+ ).first()
+ val permissionControllerPackageState: PackageState?
+ packageManagerLocal.withUnfilteredSnapshot().use { snapshot ->
+ packageState = snapshot.filtered(callingUid, userId)
+ .use { it.getPackageState(packageName) }
+ permissionControllerPackageState =
+ snapshot.getPackageState(permissionControllerPackageName)
+ }
+ val androidPackage = packageState?.androidPackage
+ // Different from the old implementation, which returns when package doesn't exist but
+ // throws when package exists but isn't visible, we now return in both cases to avoid
+ // leaking the package existence.
+ if (androidPackage == null) {
+ Log.w(LOG_TAG, "$methodName: Unknown package $packageName")
+ return
+ }
+
+ val canManageRolePermission = isRootOrSystem(callingUid) ||
+ UserHandle.getAppId(callingUid) == permissionControllerPackageState!!.appId
+ val overridePolicyFixed = context.checkCallingOrSelfPermission(
+ Manifest.permission.ADJUST_RUNTIME_PERMISSIONS_POLICY
+ ) == PackageManager.PERMISSION_GRANTED
+
+ service.mutateState {
+ with(onPermissionFlagsChangedListener) {
+ if (skipKillUid) {
+ skipKillRuntimePermissionRevokedUids()
+ }
+ if (revokeReason != null) {
+ addKillRuntimePermissionRevokedUidsReason(revokeReason)
+ }
+ }
+
+ setRuntimePermissionGranted(
+ packageState, userId, permissionName, isGranted, canManageRolePermission,
+ overridePolicyFixed, reportError = true, methodName
+ )
+ }
}
- override fun removeOnPermissionsChangeListener(listener: IOnPermissionsChangeListener) {
- onPermissionsChangeListeners.removeListener(listener)
+ private fun grantRequestedRuntimePermissions(
+ packageState: PackageState,
+ userId: Int,
+ permissionNames: List<String>
+ ) {
+ service.mutateState {
+ permissionNames.forEachIndexed { _, permissionName ->
+ setRuntimePermissionGranted(
+ packageState, userId, permissionName, isGranted = true,
+ canManageRolePermission = false, overridePolicyFixed = false,
+ reportError = false, "grantRequestedRuntimePermissions"
+ )
+ }
+ }
+ }
+
+ /**
+ * Set whether a runtime permission is granted, without any validation on caller.
+ */
+ private fun MutateStateScope.setRuntimePermissionGranted(
+ packageState: PackageState,
+ userId: Int,
+ permissionName: String,
+ isGranted: Boolean,
+ canManageRolePermission: Boolean,
+ overridePolicyFixed: Boolean,
+ reportError: Boolean,
+ methodName: String
+ ) {
+ val permission = with(policy) { getPermissions()[permissionName] }
+ if (permission == null) {
+ if (reportError) {
+ throw IllegalArgumentException("Unknown permission $permissionName")
+ }
+ return
+ }
+
+ val androidPackage = packageState.androidPackage!!
+ val packageName = packageState.packageName
+ when {
+ permission.isDevelopment -> {}
+ permission.isRole -> {
+ if (!canManageRolePermission) {
+ if (reportError) {
+ throw SecurityException("Permission $permissionName is managed by role")
+ }
+ return
+ }
+ }
+ permission.isRuntime -> {
+ if (androidPackage.targetSdkVersion < Build.VERSION_CODES.M) {
+ // If a permission review is required for legacy apps we represent
+ // their permissions as always granted
+ return
+ }
+ if (isGranted && packageState.getUserStateOrDefault(userId).isInstantApp &&
+ !permission.isInstant) {
+ if (reportError) {
+ throw SecurityException(
+ "Cannot grant non-instant permission $permissionName to package" +
+ " $packageName"
+ )
+ }
+ return
+ }
+ }
+ else -> {
+ if (reportError) {
+ throw SecurityException(
+ "Permission $permissionName requested by package $packageName is not a" +
+ " changeable permission type"
+ )
+ }
+ return
+ }
+ }
+
+ val appId = packageState.appId
+ val oldFlags = with(policy) { getPermissionFlags(appId, userId, permissionName) }
+
+ if (permissionName !in androidPackage.requestedPermissions && oldFlags == 0) {
+ if (reportError) {
+ throw SecurityException(
+ "Permission $permissionName isn't requested by package $packageName"
+ )
+ }
+ return
+ }
+
+ if (oldFlags.hasBits(PermissionFlags.SYSTEM_FIXED)) {
+ if (reportError) {
+ Log.e(
+ LOG_TAG, "$methodName: Cannot change system fixed permission $permissionName" +
+ " for package $packageName"
+ )
+ }
+ return
+ }
+
+ if (oldFlags.hasBits(PermissionFlags.POLICY_FIXED) && !overridePolicyFixed) {
+ if (reportError) {
+ Log.e(
+ LOG_TAG, "$methodName: Cannot change policy fixed permission $permissionName" +
+ " for package $packageName"
+ )
+ }
+ return
+ }
+
+ if (isGranted && oldFlags.hasBits(PermissionFlags.RESTRICTION_REVOKED)) {
+ if (reportError) {
+ Log.e(
+ LOG_TAG, "$methodName: Cannot grant hard-restricted non-exempt permission" +
+ " $permissionName to package $packageName"
+ )
+ }
+ return
+ }
+
+ if (isGranted && oldFlags.hasBits(PermissionFlags.SOFT_RESTRICTED)) {
+ // TODO: Refactor SoftRestrictedPermissionPolicy.
+ val softRestrictedPermissionPolicy = SoftRestrictedPermissionPolicy.forPermission(
+ context, AndroidPackageUtils.generateAppInfoWithoutState(androidPackage),
+ androidPackage, UserHandle.of(userId), permissionName
+ )
+ if (!softRestrictedPermissionPolicy.mayGrantPermission()) {
+ if (reportError) {
+ Log.e(
+ LOG_TAG, "$methodName: Cannot grant soft-restricted non-exempt permission" +
+ " $permissionName to package $packageName"
+ )
+ }
+ return
+ }
+ }
+
+ val newFlags = PermissionFlags.updateRuntimePermissionGranted(oldFlags, isGranted)
+ if (oldFlags == newFlags) {
+ return
+ }
+
+ with(policy) { setPermissionFlags(appId, userId, permissionName, newFlags) }
+
+ if (permission.isRuntime) {
+ val action = if (isGranted) {
+ MetricsProto.MetricsEvent.ACTION_PERMISSION_GRANTED
+ } else {
+ MetricsProto.MetricsEvent.ACTION_PERMISSION_REVOKED
+ }
+ val log = LogMaker(action).apply {
+ setPackageName(packageName)
+ addTaggedData(MetricsProto.MetricsEvent.FIELD_PERMISSION, permissionName)
+ }
+ metricsLogger.write(log)
+ }
}
override fun getPermissionFlags(packageName: String, permissionName: String, userId: Int): Int {
- // TODO: Implement permission checks.
- val appId = 0
- val flags = service.getState {
- with(policy) { getPermissionFlags(appId, userId, permissionName) }
+ enforceCallingOrSelfCrossUserPermission(
+ userId, enforceFullPermission = true, enforceShellRestriction = false,
+ "getPermissionFlags"
+ )
+ enforceCallingOrSelfAnyPermission(
+ "getPermissionFlags", Manifest.permission.GRANT_RUNTIME_PERMISSIONS,
+ Manifest.permission.REVOKE_RUNTIME_PERMISSIONS,
+ Manifest.permission.GET_RUNTIME_PERMISSIONS
+ )
+
+ if (!userManagerInternal.exists(userId)) {
+ Log.w(LOG_TAG, "getPermissionFlags: Unknown user $userId")
+ return 0
}
- return PermissionFlags.toApiFlags(flags)
+
+ val packageState = packageManagerLocal.withFilteredSnapshot()
+ .use { it.getPackageState(packageName) }
+ if (packageState == null) {
+ Log.w(LOG_TAG, "getPermissionFlags: Unknown package $packageName")
+ return 0
+ }
+
+ service.getState {
+ val permission = with(policy) { getPermissions()[permissionName] }
+ if (permission == null) {
+ Log.w(LOG_TAG, "getPermissionFlags: Unknown permission $permissionName")
+ return 0
+ }
+
+ val flags =
+ with(policy) { getPermissionFlags(packageState.appId, userId, permissionName) }
+ return PermissionFlags.toApiFlags(flags)
+ }
}
override fun isPermissionRevokedByPolicy(
@@ -330,49 +789,43 @@
permissionName: String,
userId: Int
): Boolean {
- if (UserHandle.getCallingUserId() != userId) {
- context.enforceCallingPermission(
- Manifest.permission.INTERACT_ACROSS_USERS_FULL,
- "isPermissionRevokedByPolicy for user $userId"
- )
- }
+ enforceCallingOrSelfCrossUserPermission(
+ userId, enforceFullPermission = true, enforceShellRestriction = false,
+ "isPermissionRevokedByPolicy"
+ )
- if (checkPermission(packageName, permissionName, userId) ==
- PackageManager.PERMISSION_GRANTED) {
+ if (!userManagerInternal.exists(userId)) {
+ Log.w(LOG_TAG, "isPermissionRevokedByPolicy: Unknown user $userId")
return false
}
- val callingUid = Binder.getCallingUid()
- if (packageManagerLocal.withUnfilteredSnapshot()
- .use { !it.isPackageVisibleToUid(packageName, userId, callingUid) }) {
- return false
+ val packageState = packageManagerLocal.withFilteredSnapshot(Binder.getCallingUid(), userId)
+ .use { it.getPackageState(packageName) } ?: return false
+
+ service.getState {
+ if (isPermissionGranted(packageState, userId, permissionName)) {
+ return false
+ }
+
+ val flags = with(policy) {
+ getPermissionFlags(packageState.appId, userId, permissionName)
+ }
+ return flags.hasBits(PermissionFlags.POLICY_FIXED)
}
-
- val permissionFlags = getPermissionFlagsUnchecked(packageName,
- permissionName, callingUid, userId)
- return permissionFlags.hasBits(PackageManager.FLAG_PERMISSION_POLICY_FIXED)
- }
-
- private fun getPermissionFlagsUnchecked(
- packageName: String,
- permName: String,
- callingUid: Int,
- userId: Int
- ): Int {
- throw NotImplementedError()
}
override fun isPermissionsReviewRequired(packageName: String, userId: Int): Boolean {
- requireNotNull(packageName) { "packageName" }
+ requireNotNull(packageName) { "packageName cannot be null" }
// TODO(b/173235285): Some caller may pass USER_ALL as userId.
- // Preconditions.checkArgumentNonnegative(userId, "userId");
+ // Preconditions.checkArgumentNonnegative(userId, "userId")
+
val packageState = packageManagerLocal.withUnfilteredSnapshot()
- .use { it.packageStates[packageName] } ?: return false
+ .use { it.getPackageState(packageName) } ?: return false
+
val permissionFlags = service.getState {
with(policy) { getUidPermissionFlags(packageState.appId, userId) }
} ?: return false
- return permissionFlags.anyIndexed { _, _, flags -> PermissionFlags.isReviewRequired(flags)
- }
+ return permissionFlags.anyIndexed { _, _, it -> it.hasBits(REVIEW_REQUIRED_FLAGS) }
}
override fun shouldShowRequestPermissionRationale(
@@ -380,70 +833,54 @@
permissionName: String,
userId: Int
): Boolean {
- val callingUid = Binder.getCallingUid()
- if (UserHandle.getCallingUserId() != userId) {
- context.enforceCallingPermission(
- Manifest.permission.INTERACT_ACROSS_USERS_FULL,
- "canShowRequestPermissionRationale for user $userId"
- )
+ enforceCallingOrSelfCrossUserPermission(
+ userId, enforceFullPermission = true, enforceShellRestriction = false,
+ "shouldShowRequestPermissionRationale"
+ )
+
+ if (!userManagerInternal.exists(userId)) {
+ Log.w(LOG_TAG, "shouldShowRequestPermissionRationale: Unknown user $userId")
+ return false
}
- val appId = packageManagerLocal.withUnfilteredSnapshot().use { snapshot ->
- snapshot.packageStates[packageName]?.appId ?: return false
- }
+ val callingUid = Binder.getCallingUid()
+ val packageState = packageManagerLocal.withFilteredSnapshot(callingUid, userId)
+ .use { it.getPackageState(packageName) } ?: return false
+ val appId = packageState.appId
if (UserHandle.getAppId(callingUid) != appId) {
return false
}
- if (checkPermission(packageName, permissionName, userId) ==
- PackageManager.PERMISSION_GRANTED) {
+ val flags: Int
+ service.getState {
+ if (isPermissionGranted(packageState, userId, permissionName)) {
+ return false
+ }
+
+ flags = with(policy) { getPermissionFlags(appId, userId, permissionName) }
+ }
+ if (flags.hasAnyBit(UNREQUESTABLE_MASK)) {
return false
}
- val identity = Binder.clearCallingIdentity()
- val permissionFlags = try {
- getPermissionFlagsInternal(packageName, permissionName, callingUid, userId)
- } finally {
- Binder.restoreCallingIdentity(identity)
- }
-
- val fixedFlags = (PermissionFlags.SYSTEM_FIXED or PermissionFlags.POLICY_FIXED
- or PermissionFlags.USER_FIXED)
-
- if (permissionFlags.hasAnyBit(fixedFlags) ||
- permissionFlags.hasBits(PermissionFlags.RESTRICTION_REVOKED)) {
- return false
- }
-
- val token = Binder.clearCallingIdentity()
- try {
- if (permissionName == Manifest.permission.ACCESS_BACKGROUND_LOCATION &&
- platformCompat.isChangeEnabledByPackageName(
- BACKGROUND_RATIONALE_CHANGE_ID, packageName, userId)
- ) {
+ if (permissionName == Manifest.permission.ACCESS_BACKGROUND_LOCATION) {
+ val isBackgroundRationaleChangeEnabled = Binder::class.withClearedCallingIdentity {
+ try {
+ platformCompat.isChangeEnabledByPackageName(
+ BACKGROUND_RATIONALE_CHANGE_ID, packageName, userId
+ )
+ } catch (e: RemoteException) {
+ Log.e(LOG_TAG, "shouldShowRequestPermissionRationale: Unable to check if" +
+ " compatibility change is enabled", e)
+ false
+ }
+ }
+ if (isBackgroundRationaleChangeEnabled) {
return true
}
- } catch (e: RemoteException) {
- Log.e(LOG_TAG, "Unable to check if compatibility change is enabled.", e)
- } finally {
- Binder.restoreCallingIdentity(token)
}
- return permissionFlags and PackageManager.FLAG_PERMISSION_USER_SET != 0
- }
-
- /**
- * read internal permission flags
- * @return internal permission Flags
- * @see PermissionFlags
- */
- private fun getPermissionFlagsInternal(
- packageName: String,
- permName: String,
- callingUid: Int,
- userId: Int
- ): Int {
- throw NotImplementedError()
+ return flags.hasBits(PermissionFlags.USER_SET)
}
override fun updatePermissionFlags(
@@ -451,14 +888,215 @@
permissionName: String,
flagMask: Int,
flagValues: Int,
- checkAdjustPolicyFlagPermission: Boolean,
+ enforceAdjustPolicyPermission: Boolean,
userId: Int
) {
- TODO("Not yet implemented")
+ val callingUid = Binder.getCallingUid()
+ if (PermissionManager.DEBUG_TRACE_PERMISSION_UPDATES &&
+ PermissionManager.shouldTraceGrant(packageName, permissionName, userId)) {
+ val flagMaskString = DebugUtils.flagsToString(
+ PackageManager::class.java, "FLAG_PERMISSION_", flagMask.toLong()
+ )
+ val flagValuesString = DebugUtils.flagsToString(
+ PackageManager::class.java, "FLAG_PERMISSION_", flagValues.toLong()
+ )
+ val callingUidName = packageManagerInternal.getNameForUid(callingUid)
+ Log.i(
+ LOG_TAG, "updatePermissionFlags(packageName = $packageName," +
+ " permissionName = $permissionName, flagMask = $flagMaskString," +
+ " flagValues = $flagValuesString, userId = $userId," +
+ " callingUid = $callingUidName ($callingUid))", RuntimeException()
+ )
+ }
+
+ enforceCallingOrSelfCrossUserPermission(
+ userId, enforceFullPermission = true, enforceShellRestriction = true,
+ "updatePermissionFlags"
+ )
+ enforceCallingOrSelfAnyPermission(
+ "updatePermissionFlags", Manifest.permission.GRANT_RUNTIME_PERMISSIONS,
+ Manifest.permission.REVOKE_RUNTIME_PERMISSIONS
+ )
+
+ // Different from the old implementation, which implicitly didn't allow modifying the
+ // POLICY_FIXED flag if the caller is system or root UID, now we do allow that since system
+ // and root UIDs are supposed to have all permissions including
+ // ADJUST_RUNTIME_PERMISSIONS_POLICY.
+ if (!isRootOrSystem(callingUid)) {
+ if (flagMask.hasBits(PackageManager.FLAG_PERMISSION_POLICY_FIXED)) {
+ if (enforceAdjustPolicyPermission) {
+ context.enforceCallingOrSelfPermission(
+ Manifest.permission.ADJUST_RUNTIME_PERMISSIONS_POLICY,
+ "Need ${Manifest.permission.ADJUST_RUNTIME_PERMISSIONS_POLICY} to change" +
+ " policy flags"
+ )
+ } else {
+ val targetSdkVersion = packageManagerInternal.getUidTargetSdkVersion(callingUid)
+ require(targetSdkVersion < Build.VERSION_CODES.Q) {
+ "${Manifest.permission.ADJUST_RUNTIME_PERMISSIONS_POLICY} needs to be" +
+ " checked for packages targeting ${Build.VERSION_CODES.Q} or later" +
+ " when changing policy flags"
+ }
+ }
+ }
+ }
+
+ if (!userManagerInternal.exists(userId)) {
+ Log.w(LOG_TAG, "updatePermissionFlags: Unknown user $userId")
+ return
+ }
+
+ // Using PackageManagerInternal instead of PackageManagerLocal for now due to need to access
+ // shared user packages.
+ // TODO: We probably shouldn't check the share user packages, since the package name is
+ // explicitly provided and grantRuntimePermission() isn't checking shared user packages
+ // anyway.
+ val packageState = packageManagerInternal.getPackageStateInternal(packageName)
+ val androidPackage = packageState?.androidPackage
+ // Different from the old implementation, which returns when package doesn't exist but
+ // throws when package exists but isn't visible, we now return in both cases to avoid
+ // leaking the package existence.
+ if (androidPackage == null ||
+ packageManagerInternal.filterAppAccess(packageName, callingUid, userId, false)) {
+ Log.w(LOG_TAG, "updatePermissionFlags: Unknown package $packageName")
+ return
+ }
+
+ val isPermissionRequested = if (permissionName in androidPackage.requestedPermissions) {
+ // Fast path, the current package has requested the permission.
+ true
+ } else {
+ // Slow path, go through all shared user packages.
+ val sharedUserPackageNames =
+ packageManagerInternal.getSharedUserPackagesForPackage(packageName, userId)
+ sharedUserPackageNames.any { sharedUserPackageName ->
+ val sharedUserPackage = packageManagerInternal.getPackage(sharedUserPackageName)
+ sharedUserPackage != null &&
+ permissionName in sharedUserPackage.requestedPermissions
+ }
+ }
+
+ val appId = packageState.appId
+ service.mutateState {
+ updatePermissionFlags(
+ appId, userId, permissionName, flagMask, flagValues,
+ reportErrorForUnknownPermission = true, isPermissionRequested,
+ "updatePermissionFlags", packageName
+ )
+ }
}
override fun updatePermissionFlagsForAllApps(flagMask: Int, flagValues: Int, userId: Int) {
- TODO("Not yet implemented")
+ val callingUid = Binder.getCallingUid()
+ if (PermissionManager.DEBUG_TRACE_PERMISSION_UPDATES) {
+ val flagMaskString = DebugUtils.flagsToString(
+ PackageManager::class.java, "FLAG_PERMISSION_", flagMask.toLong()
+ )
+ val flagValuesString = DebugUtils.flagsToString(
+ PackageManager::class.java, "FLAG_PERMISSION_", flagValues.toLong()
+ )
+ val callingUidName = packageManagerInternal.getNameForUid(callingUid)
+ Log.i(
+ LOG_TAG, "updatePermissionFlagsForAllApps(flagMask = $flagMaskString," +
+ " flagValues = $flagValuesString, userId = $userId," +
+ " callingUid = $callingUidName ($callingUid))", RuntimeException()
+ )
+ }
+
+ enforceCallingOrSelfCrossUserPermission(
+ userId, enforceFullPermission = true, enforceShellRestriction = true,
+ "updatePermissionFlagsForAllApps"
+ )
+ enforceCallingOrSelfAnyPermission(
+ "updatePermissionFlagsForAllApps", Manifest.permission.GRANT_RUNTIME_PERMISSIONS,
+ Manifest.permission.REVOKE_RUNTIME_PERMISSIONS
+ )
+
+ if (!userManagerInternal.exists(userId)) {
+ Log.w(LOG_TAG, "updatePermissionFlagsForAllApps: Unknown user $userId")
+ return
+ }
+
+ val packageStates = packageManagerLocal.withUnfilteredSnapshot()
+ .use { it.packageStates }
+ service.mutateState {
+ packageStates.forEach { (packageName, packageState) ->
+ val androidPackage = packageState.androidPackage ?: return@forEach
+ androidPackage.requestedPermissions.forEach { permissionName ->
+ // Different from the old implementation, which only sanitized the SYSTEM_FIXED
+ // flag, we now properly sanitize all flags as in updatePermissionFlags().
+ updatePermissionFlags(
+ packageState.appId, userId, permissionName, flagMask, flagValues,
+ reportErrorForUnknownPermission = false, isPermissionRequested = true,
+ "updatePermissionFlagsForAllApps", packageName
+ )
+ }
+ }
+ }
+ }
+
+ /**
+ * Shared internal implementation that should only be called by [updatePermissionFlags] and
+ * [updatePermissionFlagsForAllApps].
+ */
+ private fun MutateStateScope.updatePermissionFlags(
+ appId: Int,
+ userId: Int,
+ permissionName: String,
+ flagMask: Int,
+ flagValues: Int,
+ reportErrorForUnknownPermission: Boolean,
+ isPermissionRequested: Boolean,
+ methodName: String,
+ packageName: String
+ ) {
+ // Different from the old implementation, which only allowed the system UID to modify the
+ // following flags, we now allow the root UID as well since both should have all
+ // permissions.
+ // Only the system can change these flags and nothing else.
+ val callingUid = Binder.getCallingUid()
+ @Suppress("NAME_SHADOWING")
+ var flagMask = flagMask
+ @Suppress("NAME_SHADOWING")
+ var flagValues = flagValues
+ if (!isRootOrSystem(callingUid)) {
+ // Different from the old implementation, which allowed non-system UIDs to remove (but
+ // not add) permission restriction flags, we now consistently ignore them altogether.
+ val ignoredMask = PackageManager.FLAG_PERMISSION_SYSTEM_FIXED or
+ PackageManager.FLAG_PERMISSION_GRANTED_BY_DEFAULT or
+ // REVIEW_REQUIRED can be set on any permission by the shell, or by any app for the
+ // NOTIFICATIONS permissions specifically.
+ if (isShell(callingUid) || permissionName in NOTIFICATIONS_PERMISSIONS) {
+ 0
+ } else {
+ PackageManager.FLAG_PERMISSION_REVIEW_REQUIRED
+ } or PackageManager.FLAG_PERMISSION_RESTRICTION_SYSTEM_EXEMPT or
+ PackageManager.FLAG_PERMISSION_RESTRICTION_INSTALLER_EXEMPT or
+ PackageManager.FLAG_PERMISSION_RESTRICTION_UPGRADE_EXEMPT or
+ PackageManager.FLAG_PERMISSION_APPLY_RESTRICTION
+ flagMask = flagMask andInv ignoredMask
+ flagValues = flagValues andInv ignoredMask
+ }
+
+ val permission = with(policy) { getPermissions()[permissionName] }
+ if (permission == null) {
+ if (reportErrorForUnknownPermission) {
+ throw IllegalArgumentException("Unknown permission $permissionName")
+ }
+ return
+ }
+
+ val oldFlags = with(policy) { getPermissionFlags(appId, userId, permissionName) }
+ if (!isPermissionRequested && oldFlags == 0) {
+ Log.w(
+ LOG_TAG, "$methodName: Permission $permissionName isn't requested by package" +
+ " $packageName"
+ )
+ return
+ }
+
+ val newFlags = PermissionFlags.updateFlags(permission, oldFlags, flagMask, flagValues)
+ with(policy) { setPermissionFlags(appId, userId, permissionName, newFlags) }
}
override fun addAllowlistedRestrictedPermission(
@@ -495,21 +1133,29 @@
TODO("Not yet implemented")
}
+ override fun addOnPermissionsChangeListener(listener: IOnPermissionsChangeListener) {
+ onPermissionsChangeListeners.addListener(listener)
+ }
+
+ override fun removeOnPermissionsChangeListener(listener: IOnPermissionsChangeListener) {
+ onPermissionsChangeListeners.removeListener(listener)
+ }
+
override fun addOnRuntimePermissionStateChangedListener(
listener: PermissionManagerServiceInternal.OnRuntimePermissionStateChangedListener
) {
- TODO("Not yet implemented")
+ // TODO: Should be removed once we remove PermissionPolicyService.
}
override fun removeOnRuntimePermissionStateChangedListener(
listener: PermissionManagerServiceInternal.OnRuntimePermissionStateChangedListener
) {
- TODO("Not yet implemented")
+ // TODO: Should be removed once we remove PermissionPolicyService.
}
override fun getSplitPermissions(): List<SplitPermissionInfoParcelable> {
return PermissionManager.splitPermissionInfoListToParcelableList(
- SystemConfig.getInstance().splitPermissions
+ systemConfig.splitPermissions
)
}
@@ -536,10 +1182,6 @@
return appOpPermissionPackageNames
}
- override fun getGidsForUid(uid: Int): IntArray {
- TODO("Not yet implemented")
- }
-
override fun backupRuntimePermissions(userId: Int): ByteArray? {
TODO("Not yet implemented")
}
@@ -635,6 +1277,8 @@
synchronized(mountedStorageVolumes) {
if (androidPackage.volumeUuid !in mountedStorageVolumes) {
// Wait for the storage volume to be mounted and batch the state mutation there.
+ // PackageInstalledParams won't exist when packages are being scanned instead of
+ // being installed by an installer.
return
}
}
@@ -643,8 +1287,15 @@
} else {
intArrayOf(userId)
}
- userIds.forEach { service.onPackageInstalled(androidPackage.packageName, it) }
- // TODO: Handle params.
+ @Suppress("NAME_SHADOWING")
+ userIds.forEach { userId ->
+ service.onPackageInstalled(androidPackage.packageName, userId)
+ // TODO: Remove when this callback receives packageState directly.
+ val packageState =
+ packageManagerInternal.getPackageStateInternal(androidPackage.packageName)!!
+ // TODO: Add allowlisting
+ grantRequestedRuntimePermissions(packageState, userId, params.grantedPermissions)
+ }
}
override fun onPackageUninstalled(
@@ -666,6 +1317,24 @@
}
}
+ /**
+ * Check whether a UID is root or system.
+ */
+ private fun isRootOrSystem(uid: Int) =
+ when (UserHandle.getAppId(uid)) {
+ Process.ROOT_UID, Process.SYSTEM_UID -> true
+ else -> false
+ }
+
+ /**
+ * Check whether a UID is shell.
+ */
+ private fun isShell(uid: Int) = UserHandle.getAppId(uid) == Process.SHELL_UID
+
+ /**
+ * Check whether a UID is root, system or shell.
+ */
+ private fun isRootOrSystemOrShell(uid: Int) = isRootOrSystem(uid) || isShell(uid)
/**
* This method should typically only be used when granting or revoking permissions, since the
@@ -674,9 +1343,11 @@
* If you're doing surgery on app code/data, use [PackageFreezer] to guard your work against
* the app being relaunched.
*/
- private fun killUid(appId: Int, userId: Int, reason: String) {
+ private fun killUid(uid: Int, reason: String) {
val activityManager = ActivityManager.getService()
if (activityManager != null) {
+ val appId = UserHandle.getAppId(uid)
+ val userId = UserHandle.getUserId(uid)
val identity = Binder.clearCallingIdentity()
try {
activityManager.killUidForPermissionChange(appId, userId, reason)
@@ -689,21 +1360,33 @@
}
/**
+ * @see PackageManagerLocal.withFilteredSnapshot
+ */
+ private fun PackageManagerLocal.withFilteredSnapshot(
+ callingUid: Int,
+ userId: Int
+ ): PackageManagerLocal.FilteredSnapshot =
+ withFilteredSnapshot(callingUid, UserHandle.of(userId))
+
+ /**
+ * Get the [PackageState] for a package name.
+ *
+ * This is for parity with [PackageManagerLocal.FilteredSnapshot.getPackageState] which is more
+ * efficient than [PackageManagerLocal.FilteredSnapshot.getPackageStates], so that we can always
+ * prefer using `getPackageState()` without worrying about whether the snapshot is filtered.
+ */
+ private fun PackageManagerLocal.UnfilteredSnapshot.getPackageState(
+ packageName: String
+ ): PackageState? = packageStates[packageName]
+
+ /**
* Check whether a UID belongs to an instant app.
*/
- private fun PackageManagerLocal.UnfilteredSnapshot.isUidInstantApp(uid: Int): Boolean {
- if (Process.isIsolatedUid(uid)) {
- // Unfortunately we don't have the API for getting the owner UID of an isolated UID yet,
- // so for now we just keep calling the old API.
- return packageManagerInternal.getInstantAppPackageName(uid) != null
- }
- val appId = UserHandle.getAppId(uid)
- // Instant apps can't have shared UIDs, so we can just take the first package.
- val firstPackageState = packageStates.values.firstOrNull { it.appId == appId }
- ?: return false
- val userId = UserHandle.getUserId(uid)
- return firstPackageState.getUserStateOrDefault(userId).isInstantApp
- }
+ private fun PackageManagerLocal.UnfilteredSnapshot.isUidInstantApp(uid: Int): Boolean =
+ // Unfortunately we don't have the API for getting the owner UID of an isolated UID or the
+ // API for getting the SharedUserApi object for an app ID yet, so for now we just keep
+ // calling the old API.
+ packageManagerInternal.getInstantAppPackageName(uid) != null
/**
* Check whether a package is visible to a UID within the same user as the UID.
@@ -720,16 +1403,123 @@
packageName: String,
userId: Int,
uid: Int
- ): Boolean {
- val user = UserHandle.of(userId)
- return filtered(uid, user).use { it.getPackageState(packageName) != null }
+ ): Boolean = filtered(uid, userId).use { it.getPackageState(packageName) != null }
+
+ /**
+ * @see PackageManagerLocal.UnfilteredSnapshot.filtered
+ */
+ private fun PackageManagerLocal.UnfilteredSnapshot.filtered(
+ callingUid: Int,
+ userId: Int
+ ): PackageManagerLocal.FilteredSnapshot = filtered(callingUid, UserHandle.of(userId))
+
+ /**
+ * If neither you nor the calling process of an IPC you are handling has been granted the
+ * permission for accessing a particular [userId], throw a [SecurityException].
+ *
+ * @see Context.enforceCallingOrSelfPermission
+ * @see UserManager.DISALLOW_DEBUGGING_FEATURES
+ */
+ private fun enforceCallingOrSelfCrossUserPermission(
+ userId: Int,
+ enforceFullPermission: Boolean,
+ enforceShellRestriction: Boolean,
+ message: String?
+ ) {
+ require(userId >= 0) { "userId $userId is invalid" }
+ val callingUid = Binder.getCallingUid()
+ val callingUserId = UserHandle.getUserId(callingUid)
+ if (userId != callingUserId) {
+ val permissionName = if (enforceFullPermission) {
+ Manifest.permission.INTERACT_ACROSS_USERS_FULL
+ } else {
+ Manifest.permission.INTERACT_ACROSS_USERS
+ }
+ if (context.checkCallingOrSelfPermission(permissionName) !=
+ PackageManager.PERMISSION_GRANTED) {
+ val exceptionMessage = buildString {
+ if (message != null) {
+ append(message)
+ append(": ")
+ }
+ append("Neither user ")
+ append(Binder.getCallingUid())
+ append(" nor current process has ")
+ append(permissionName)
+ append(" to access user ")
+ append(userId)
+ }
+ throw SecurityException(exceptionMessage)
+ }
+ }
+ if (enforceShellRestriction && isShell(callingUid)) {
+ val isShellRestricted = userManagerInternal.hasUserRestriction(
+ UserManager.DISALLOW_DEBUGGING_FEATURES, userId
+ )
+ if (isShellRestricted) {
+ val exceptionMessage = buildString {
+ if (message != null) {
+ append(message)
+ append(": ")
+ }
+ append("Shell is disallowed to access user ")
+ append(userId)
+ }
+ throw SecurityException(exceptionMessage)
+ }
+ }
+ }
+
+ /**
+ * If neither you nor the calling process of an IPC you are handling has been granted any of the
+ * permissions, throw a [SecurityException].
+ *
+ * @see Context.enforceCallingOrSelfPermission
+ */
+ private fun enforceCallingOrSelfAnyPermission(
+ message: String?,
+ vararg permissionNames: String
+ ) {
+ val hasAnyPermission = permissionNames.any { permissionName ->
+ context.checkCallingOrSelfPermission(permissionName) ==
+ PackageManager.PERMISSION_GRANTED
+ }
+ if (!hasAnyPermission) {
+ val exceptionMessage = buildString {
+ if (message != null) {
+ append(message)
+ append(": ")
+ }
+ append("Neither user ")
+ append(Binder.getCallingUid())
+ append(" nor current process has any of ")
+ permissionNames.joinTo(this, ", ")
+ }
+ throw SecurityException(exceptionMessage)
+ }
}
/**
* Callback invoked when interesting actions have been taken on a permission.
*/
private inner class OnPermissionFlagsChangedListener :
- UidPermissionPolicy.OnPermissionFlagsChangedListener {
+ UidPermissionPolicy.OnPermissionFlagsChangedListener() {
+ private val runtimePermissionChangedUids = IntSet()
+ // Mapping from UID to whether only notifications permissions are revoked.
+ private val runtimePermissionRevokedUids = IntBooleanMap()
+ private val gidsChangedUids = IntSet()
+
+ private var isKillRuntimePermissionRevokedUidsSkipped = false
+ private val killRuntimePermissionRevokedUidsReasons = IndexedSet<String>()
+
+ fun MutateStateScope.skipKillRuntimePermissionRevokedUids() {
+ isKillRuntimePermissionRevokedUidsSkipped = true
+ }
+
+ fun MutateStateScope.addKillRuntimePermissionRevokedUidsReason(reason: String) {
+ killRuntimePermissionRevokedUidsReasons += reason
+ }
+
override fun onPermissionFlagsChanged(
appId: Int,
userId: Int,
@@ -741,37 +1531,60 @@
val permission = service.getState {
with(policy) { getPermissions()[permissionName] }
} ?: return
+ val wasPermissionGranted = PermissionFlags.isPermissionGranted(oldFlags)
+ val isPermissionGranted = PermissionFlags.isPermissionGranted(newFlags)
- val isPermissionGranted = !PermissionFlags.isPermissionGranted(oldFlags) &&
- PermissionFlags.isPermissionGranted(newFlags)
- val isPermissionRevoked = PermissionFlags.isPermissionGranted(oldFlags) &&
- !PermissionFlags.isPermissionGranted(newFlags)
+ if (permission.isRuntime) {
+ // Different from the old implementation, which notifies the listeners when the
+ // permission flags have changed for a non-runtime permission, now we no longer do
+ // that because permission flags are only for runtime permissions and the listeners
+ // aren't being notified of non-runtime permission grant state changes anyway.
+ runtimePermissionChangedUids += uid
+ if (wasPermissionGranted && !isPermissionGranted) {
+ runtimePermissionRevokedUids[uid] =
+ permissionName in NOTIFICATIONS_PERMISSIONS &&
+ runtimePermissionRevokedUids.getWithDefault(uid, true)
+ }
+ }
- if (isPermissionGranted) {
- if (permission.isRuntime) {
- onPermissionsChangeListeners.onPermissionsChanged(uid)
- }
- handler.post {
- if (permission.hasGids) {
- killUid(appId, userId, PermissionManager.KILL_APP_REASON_GIDS_CHANGED)
- }
- }
- } else if (isPermissionRevoked) {
- // TODO: STOPSHIP skip kill for revokePostNotificationPermissionWithoutKillForTest
- if (permission.isRuntime) {
- onPermissionsChangeListeners.onPermissionsChanged(uid)
- handler.post {
- if (!(permissionName == Manifest.permission.POST_NOTIFICATIONS &&
- isAppBackupAndRestoreRunning(uid))) {
- killUid(
- appId, userId, PermissionManager.KILL_APP_REASON_PERMISSIONS_REVOKED
- )
- }
- }
- }
- } else if (oldFlags != newFlags) {
+ if (permission.hasGids && !wasPermissionGranted && isPermissionGranted) {
+ gidsChangedUids += uid
+ }
+ }
+
+ override fun onStateMutated() {
+ runtimePermissionChangedUids.forEachIndexed { _, uid ->
onPermissionsChangeListeners.onPermissionsChanged(uid)
}
+ runtimePermissionChangedUids.clear()
+
+ if (!isKillRuntimePermissionRevokedUidsSkipped) {
+ val reason = if (killRuntimePermissionRevokedUidsReasons.isNotEmpty()) {
+ killRuntimePermissionRevokedUidsReasons.joinToString(", ")
+ } else {
+ PermissionManager.KILL_APP_REASON_PERMISSIONS_REVOKED
+ }
+ runtimePermissionRevokedUids.forEachIndexed {
+ _, uid, areOnlyNotificationsPermissionsRevoked ->
+ handler.post {
+ if (areOnlyNotificationsPermissionsRevoked &&
+ isAppBackupAndRestoreRunning(uid)
+ ) {
+ return@post
+ }
+ killUid(uid, reason)
+ }
+ }
+ }
+ runtimePermissionRevokedUids.clear()
+
+ gidsChangedUids.forEachIndexed { _, uid ->
+ handler.post { killUid(uid, PermissionManager.KILL_APP_REASON_GIDS_CHANGED) }
+ }
+ gidsChangedUids.clear()
+
+ isKillRuntimePermissionRevokedUidsSkipped = false
+ killRuntimePermissionRevokedUidsReasons.clear()
}
private fun isAppBackupAndRestoreRunning(uid: Int): Boolean {
@@ -780,13 +1593,13 @@
return false
}
return try {
+ val contentResolver = context.contentResolver
val userId = UserHandle.getUserId(uid)
val isInSetup = Settings.Secure.getIntForUser(
- context.contentResolver, Settings.Secure.USER_SETUP_COMPLETE, userId
+ contentResolver, Settings.Secure.USER_SETUP_COMPLETE, userId
) == 0
val isInDeferredSetup = Settings.Secure.getIntForUser(
- context.contentResolver,
- Settings.Secure.USER_SETUP_PERSONALIZATION_STATE, userId
+ contentResolver, Settings.Secure.USER_SETUP_PERSONALIZATION_STATE, userId
) == Settings.Secure.USER_SETUP_PERSONALIZATION_STARTED
isInSetup || isInDeferredSetup
} catch (e: Settings.SettingNotFoundException) {
@@ -809,18 +1622,12 @@
}
private fun handleOnPermissionsChanged(uid: Int) {
- val count = listeners.beginBroadcast()
- try {
- for (i in 0 until count) {
- val callback = listeners.getBroadcastItem(i)
- try {
- callback.onPermissionsChanged(uid)
- } catch (e: RemoteException) {
- Log.e(LOG_TAG, "Permission listener is dead", e)
- }
+ listeners.broadcast { listener ->
+ try {
+ listener.onPermissionsChanged(uid)
+ } catch (e: RemoteException) {
+ Log.e(LOG_TAG, "Error when calling OnPermissionsChangeListener", e)
}
- } finally {
- listeners.finishBroadcast()
}
}
@@ -837,6 +1644,7 @@
obtainMessage(MSG_ON_PERMISSIONS_CHANGED, uid, 0).sendToTarget()
}
}
+
companion object {
private const val MSG_ON_PERMISSIONS_CHANGED = 1
}
@@ -852,5 +1660,22 @@
@ChangeId
@EnabledAfter(targetSdkVersion = Build.VERSION_CODES.Q)
private val BACKGROUND_RATIONALE_CHANGE_ID = 147316723L
+
+ private val FULLER_PERMISSIONS = IndexedMap<String, String>().apply {
+ this[Manifest.permission.ACCESS_COARSE_LOCATION] =
+ Manifest.permission.ACCESS_FINE_LOCATION
+ this[Manifest.permission.INTERACT_ACROSS_USERS] =
+ Manifest.permission.INTERACT_ACROSS_USERS_FULL
+ }
+
+ private val NOTIFICATIONS_PERMISSIONS = indexedSetOf(
+ Manifest.permission.POST_NOTIFICATIONS
+ )
+
+ private const val REVIEW_REQUIRED_FLAGS = PermissionFlags.LEGACY_GRANTED or
+ PermissionFlags.IMPLICIT
+ private const val UNREQUESTABLE_MASK = PermissionFlags.RESTRICTION_REVOKED or
+ PermissionFlags.SYSTEM_FIXED or PermissionFlags.POLICY_FIXED or
+ PermissionFlags.USER_FIXED
}
}
diff --git a/services/permission/java/com/android/server/permission/access/permission/UidPermissionPolicy.kt b/services/permission/java/com/android/server/permission/access/permission/UidPermissionPolicy.kt
index e6636e4..49759c0 100644
--- a/services/permission/java/com/android/server/permission/access/permission/UidPermissionPolicy.kt
+++ b/services/permission/java/com/android/server/permission/access/permission/UidPermissionPolicy.kt
@@ -74,6 +74,42 @@
setPermissionFlags(subject.appId, subject.userId, `object`.permissionName, decision)
}
+ override fun GetStateScope.onStateMutated() {
+ onPermissionFlagsChangedListeners.forEachIndexed { _, it -> it.onStateMutated() }
+ }
+
+ override fun MutateStateScope.onInitialized() {
+ newState.systemState.configPermissions.forEach { (permissionName, permissionEntry) ->
+ val permissions = newState.systemState.permissions
+ val oldPermission = permissions[permissionName]
+ val newPermission = if (oldPermission != null) {
+ if (permissionEntry.gids != null) {
+ oldPermission.copy(
+ gids = permissionEntry.gids, areGidsPerUser = permissionEntry.perUser
+ )
+ } else {
+ return@forEach
+ }
+ } else {
+ @Suppress("DEPRECATION")
+ val permissionInfo = PermissionInfo().apply {
+ name = permissionName
+ packageName = PLATFORM_PACKAGE_NAME
+ protectionLevel = PermissionInfo.PROTECTION_SIGNATURE
+ }
+ if (permissionEntry.gids != null) {
+ Permission(
+ permissionInfo, false, Permission.TYPE_CONFIG, 0, permissionEntry.gids,
+ permissionEntry.perUser
+ )
+ } else {
+ Permission(permissionInfo, false, Permission.TYPE_CONFIG, 0)
+ }
+ }
+ permissions[permissionName] = newPermission
+ }
+ }
+
override fun MutateStateScope.onUserAdded(userId: Int) {
newState.systemState.packageStates.forEach { (_, packageState) ->
evaluateAllPermissionStatesForPackageAndUser(packageState, userId, null)
@@ -138,6 +174,7 @@
addPermissionGroups(packageState)
addPermissions(packageState, changedPermissionNames)
// TODO: revokeStoragePermissionsIfScopeExpandedInternal()
+ // TODO: revokeSystemAlertWindowIfUpgradedPast23()
trimPermissions(packageState.packageName, changedPermissionNames)
trimPermissionStates(packageState.appId)
changedPermissionNames.forEachIndexed { _, permissionName ->
@@ -344,6 +381,8 @@
if (packageState != null && androidPackage == null) {
return
}
+ // TODO: STOPSHIP: We may need to retain permission definitions by disabled system packages
+ // to retain their permission state.
val isPermissionTreeRemoved = systemState.permissionTrees.removeAllIndexed {
_, permissionTreeName, permissionTree ->
@@ -413,6 +452,7 @@
val requestedPermissions = IndexedSet<String>()
forEachPackageInAppId(appId) {
requestedPermissions += it.androidPackage!!.requestedPermissions
+ // TODO: STOPSHIP: Retain permissions requested by disabled system packages.
}
newState.userStates.forEachIndexed { _, userId, userState ->
userState.uidPermissionFlags[appId].forEachReversedIndexed { _, permissionName, _ ->
@@ -552,18 +592,27 @@
} else if (permission.isRuntime) {
var newFlags = oldFlags and PermissionFlags.MASK_RUNTIME
if (getAppIdTargetSdkVersion(appId, permissionName) < Build.VERSION_CODES.M) {
- newFlags = newFlags or PermissionFlags.LEGACY_GRANTED
- // Explicitly check against the old state to determine if this permission is new.
- val isNewPermission =
- getOldStatePermissionFlags(appId, userId, permissionName) == 0
- if (isNewPermission) {
- newFlags = newFlags or PermissionFlags.IMPLICIT
+ if (permission.isRuntimeOnly) {
+ // Different from the old implementation, which simply skips a runtime-only
+ // permission, we now only allow holding on to the restriction related flags,
+ // since such flags may only be set one-time in some cases, and disallow all
+ // other flags thus keeping it revoked.
+ newFlags = newFlags and PermissionFlags.MASK_EXEMPT
+ } else {
+ newFlags = newFlags or PermissionFlags.LEGACY_GRANTED
+ // Explicitly check against the old state to determine if this permission is
+ // new.
+ val isNewPermission =
+ getOldStatePermissionFlags(appId, userId, permissionName) == 0
+ if (isNewPermission) {
+ newFlags = newFlags or PermissionFlags.IMPLICIT
+ }
}
} else {
newFlags = newFlags andInv PermissionFlags.LEGACY_GRANTED
val wasGrantedByImplicit = newFlags.hasBits(PermissionFlags.IMPLICIT_GRANTED)
val isLeanBackNotificationsPermission = newState.systemState.isLeanback &&
- permissionName in NOTIFICATION_PERMISSIONS
+ permissionName in NOTIFICATIONS_PERMISSIONS
val isImplicitPermission = anyPackageInAppId(appId) {
permissionName in it.androidPackage!!.implicitPermissions
}
@@ -739,8 +788,7 @@
if (!androidPackage.isPrivileged) {
return true
}
- if (permission.packageName !in
- newState.systemState.privilegedPermissionAllowlistSourcePackageNames) {
+ if (permission.packageName !in newState.systemState.privilegedPermissionAllowlistPackages) {
return true
}
val allowlistState = getPrivilegedPermissionAllowlistState(androidPackage, permission.name)
@@ -1108,18 +1156,35 @@
Manifest.permission.BLUETOOTH_SCAN
)
- private val NOTIFICATION_PERMISSIONS = indexedSetOf(
+ private val NOTIFICATIONS_PERMISSIONS = indexedSetOf(
Manifest.permission.POST_NOTIFICATIONS
)
}
- fun interface OnPermissionFlagsChangedListener {
- fun onPermissionFlagsChanged(
+ /**
+ * Listener for permission flags changes.
+ */
+ abstract class OnPermissionFlagsChangedListener {
+ /**
+ * Called when a permission flags change has been made to the upcoming new state.
+ *
+ * Implementations should keep this method fast to avoid stalling the locked state mutation,
+ * and only call external code after [onStateMutated] when the new state has actually become
+ * the current state visible to external code.
+ */
+ abstract fun onPermissionFlagsChanged(
appId: Int,
userId: Int,
permissionName: String,
oldFlags: Int,
newFlags: Int
)
+
+ /**
+ * Called when the upcoming new state has become the current state.
+ *
+ * Implementations should keep this method fast to avoid stalling the locked state mutation.
+ */
+ abstract fun onStateMutated()
}
}
diff --git a/services/permission/java/com/android/server/permission/access/util/BinderExtensions.kt b/services/permission/java/com/android/server/permission/access/util/BinderExtensions.kt
new file mode 100644
index 0000000..a55897d
--- /dev/null
+++ b/services/permission/java/com/android/server/permission/access/util/BinderExtensions.kt
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2022 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.permission.access.util
+
+import android.os.Binder
+import kotlin.reflect.KClass
+
+inline fun <R> KClass<Binder>.withClearedCallingIdentity(action: () -> R): R {
+ val token = Binder.clearCallingIdentity()
+ try {
+ return action()
+ } finally {
+ Binder.restoreCallingIdentity(token)
+ }
+}
diff --git a/services/permission/java/com/android/server/permission/access/util/RemoteCallbackListExtensions.kt b/services/permission/java/com/android/server/permission/access/util/RemoteCallbackListExtensions.kt
new file mode 100644
index 0000000..61f33e0
--- /dev/null
+++ b/services/permission/java/com/android/server/permission/access/util/RemoteCallbackListExtensions.kt
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2022 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.permission.access.util
+
+import android.os.IInterface
+import android.os.RemoteCallbackList
+
+inline fun <T : IInterface> RemoteCallbackList<T>.broadcast(action: (T) -> Unit) {
+ val itemCount = beginBroadcast()
+ try {
+ for (i in 0 until itemCount) {
+ action(getBroadcastItem(i))
+ }
+ } finally {
+ finishBroadcast()
+ }
+}
diff --git a/services/tests/mockingservicestests/src/com/android/server/job/JobSchedulerServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/job/JobSchedulerServiceTest.java
index 2c10329..6374c66 100644
--- a/services/tests/mockingservicestests/src/com/android/server/job/JobSchedulerServiceTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/job/JobSchedulerServiceTest.java
@@ -254,11 +254,6 @@
ConnectivityController connectivityController = mService.getConnectivityController();
spyOn(connectivityController);
- mService.mConstants.RUNTIME_MIN_DATA_TRANSFER_GUARANTEE_MS = 10 * MINUTE_IN_MILLIS;
- mService.mConstants.RUNTIME_DATA_TRANSFER_LIMIT_MS = 60 * MINUTE_IN_MILLIS;
- mService.mConstants.RUNTIME_MIN_USER_INITIATED_DATA_TRANSFER_GUARANTEE_BUFFER_FACTOR = 1.5f;
- mService.mConstants.RUNTIME_MIN_USER_INITIATED_DATA_TRANSFER_GUARANTEE_MS = HOUR_IN_MILLIS;
- mService.mConstants.RUNTIME_USER_INITIATED_DATA_TRANSFER_LIMIT_MS = 6 * HOUR_IN_MILLIS;
assertEquals(mService.mConstants.RUNTIME_MIN_EJ_GUARANTEE_MS,
mService.getMinJobExecutionGuaranteeMs(ejMax));
@@ -311,7 +306,8 @@
.when(connectivityController).getEstimatedTransferTimeMs(any());
assertEquals(
(long) (mService.mConstants.RUNTIME_MIN_USER_INITIATED_DATA_TRANSFER_GUARANTEE_MS
- * 2 * 1.5),
+ * 2 * mService.mConstants
+ .RUNTIME_MIN_USER_INITIATED_DATA_TRANSFER_GUARANTEE_BUFFER_FACTOR),
mService.getMinJobExecutionGuaranteeMs(jobUIDT));
doReturn(mService.mConstants.RUNTIME_USER_INITIATED_DATA_TRANSFER_LIMIT_MS * 2)
.when(connectivityController).getEstimatedTransferTimeMs(any());
diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/MockSystem.kt b/services/tests/mockingservicestests/src/com/android/server/pm/MockSystem.kt
index 4f56271..6dc45c3 100644
--- a/services/tests/mockingservicestests/src/com/android/server/pm/MockSystem.kt
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/MockSystem.kt
@@ -55,6 +55,7 @@
import com.android.dx.mockito.inline.extended.StaticMockitoSession
import com.android.dx.mockito.inline.extended.StaticMockitoSessionBuilder
import com.android.internal.R
+import com.android.server.LocalManagerRegistry
import com.android.server.LocalServices
import com.android.server.LockGuard
import com.android.server.SystemConfig
@@ -148,6 +149,7 @@
.mockStatic(LockGuard::class.java)
.mockStatic(EventLog::class.java)
.mockStatic(LocalServices::class.java)
+ .mockStatic(LocalManagerRegistry::class.java)
.mockStatic(DeviceConfig::class.java)
.mockStatic(HexEncoding::class.java)
.apply(withSession)
diff --git a/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceParamsTest.java b/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceParamsTest.java
index aefe4b6..0f95231 100644
--- a/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceParamsTest.java
+++ b/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceParamsTest.java
@@ -17,6 +17,7 @@
package com.android.server.companion.virtual;
import static android.companion.virtual.VirtualDeviceParams.DEVICE_POLICY_CUSTOM;
+import static android.companion.virtual.VirtualDeviceParams.POLICY_TYPE_AUDIO;
import static android.companion.virtual.VirtualDeviceParams.POLICY_TYPE_SENSORS;
import static android.hardware.Sensor.TYPE_ACCELEROMETER;
@@ -47,6 +48,7 @@
.setLockState(VirtualDeviceParams.LOCK_STATE_ALWAYS_UNLOCKED)
.setUsersWithMatchingAccounts(Set.of(UserHandle.of(123), UserHandle.of(456)))
.setDevicePolicy(POLICY_TYPE_SENSORS, DEVICE_POLICY_CUSTOM)
+ .setDevicePolicy(POLICY_TYPE_AUDIO, DEVICE_POLICY_CUSTOM)
.addVirtualSensorConfig(
new VirtualSensorConfig.Builder(TYPE_ACCELEROMETER, SENSOR_NAME)
.setVendor(SENSOR_VENDOR)
@@ -62,6 +64,7 @@
assertThat(params.getUsersWithMatchingAccounts())
.containsExactly(UserHandle.of(123), UserHandle.of(456));
assertThat(params.getDevicePolicy(POLICY_TYPE_SENSORS)).isEqualTo(DEVICE_POLICY_CUSTOM);
+ assertThat(params.getDevicePolicy(POLICY_TYPE_AUDIO)).isEqualTo(DEVICE_POLICY_CUSTOM);
List<VirtualSensorConfig> sensorConfigs = params.getVirtualSensorConfigs();
assertThat(sensorConfigs).hasSize(1);
diff --git a/services/tests/servicestests/src/com/android/server/display/AutomaticBrightnessControllerTest.java b/services/tests/servicestests/src/com/android/server/display/AutomaticBrightnessControllerTest.java
index ae36871..ddde385 100644
--- a/services/tests/servicestests/src/com/android/server/display/AutomaticBrightnessControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/display/AutomaticBrightnessControllerTest.java
@@ -26,6 +26,7 @@
import static org.mockito.Mockito.anyFloat;
import static org.mockito.Mockito.anyInt;
import static org.mockito.Mockito.clearInvocations;
+import static org.mockito.Mockito.never;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoMoreInteractions;
@@ -95,7 +96,8 @@
mLightSensor = TestUtils.createSensor(Sensor.TYPE_LIGHT, "Light Sensor");
mContext = InstrumentationRegistry.getContext();
- mController = setupController(mLightSensor);
+ mController = setupController(mLightSensor, BrightnessMappingStrategy.NO_USER_LUX,
+ BrightnessMappingStrategy.NO_USER_BRIGHTNESS);
}
@After
@@ -107,7 +109,8 @@
}
}
- private AutomaticBrightnessController setupController(Sensor lightSensor) {
+ private AutomaticBrightnessController setupController(Sensor lightSensor, float userLux,
+ float userBrightness) {
mClock = new OffsettableClock.Stopped();
mTestLooper = new TestLooper(mClock::now);
@@ -132,7 +135,7 @@
mAmbientBrightnessThresholds, mScreenBrightnessThresholds,
mAmbientBrightnessThresholdsIdle, mScreenBrightnessThresholdsIdle,
mContext, mHbmController, mBrightnessThrottler, mIdleBrightnessMappingStrategy,
- AMBIENT_LIGHT_HORIZON_SHORT, AMBIENT_LIGHT_HORIZON_LONG
+ AMBIENT_LIGHT_HORIZON_SHORT, AMBIENT_LIGHT_HORIZON_LONG, userLux, userBrightness
);
when(mHbmController.getCurrentBrightnessMax()).thenReturn(BRIGHTNESS_MAX_FLOAT);
@@ -143,9 +146,10 @@
// Configure the brightness controller and grab an instance of the sensor listener,
// through which we can deliver fake (for test) sensor values.
- controller.configure(AUTO_BRIGHTNESS_ENABLED, null /* configuration */,
- 0 /* brightness */, false /* userChangedBrightness */, 0 /* adjustment */,
- false /* userChanged */, DisplayPowerRequest.POLICY_BRIGHT);
+ controller.configure(AUTO_BRIGHTNESS_ENABLED, null /* configuration= */,
+ 0 /* brightness= */, false /* userChangedBrightness= */, 0 /* adjustment= */,
+ false /* userChanged= */, DisplayPowerRequest.POLICY_BRIGHT,
+ /* shouldResetShortTermModel= */ true);
return controller;
}
@@ -250,9 +254,10 @@
listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, 1000));
// User sets brightness to 100
- mController.configure(AUTO_BRIGHTNESS_ENABLED, null /* configuration */,
- 0.5f /* brightness */, true /* userChangedBrightness */, 0 /* adjustment */,
- false /* userChanged */, DisplayPowerRequest.POLICY_BRIGHT);
+ mController.configure(AUTO_BRIGHTNESS_ENABLED, null /* configuration= */,
+ 0.5f /* brightness= */, true /* userChangedBrightness= */, 0 /* adjustment= */,
+ false /* userChanged= */, DisplayPowerRequest.POLICY_BRIGHT,
+ /* shouldResetShortTermModel= */ true);
// There should be a user data point added to the mapper.
verify(mBrightnessMappingStrategy).addUserDataPoint(1000f, 0.5f);
@@ -272,9 +277,10 @@
// User sets brightness to 0.5f
when(mBrightnessMappingStrategy.getBrightness(currentLux,
null, ApplicationInfo.CATEGORY_UNDEFINED)).thenReturn(0.5f);
- mController.configure(AUTO_BRIGHTNESS_ENABLED, null /* configuration */,
- 0.5f /* brightness */, true /* userChangedBrightness */, 0 /* adjustment */,
- false /* userChanged */, DisplayPowerRequest.POLICY_BRIGHT);
+ mController.configure(AUTO_BRIGHTNESS_ENABLED, null /* configuration= */,
+ 0.5f /* brightness= */, true /* userChangedBrightness= */, 0 /* adjustment= */,
+ false /* userChanged= */, DisplayPowerRequest.POLICY_BRIGHT,
+ /* shouldResetShortTermModel= */ true);
//Recalculating the spline with RBC enabled, verifying that the short term model is reset,
//and the interaction is learnt in short term model
@@ -305,9 +311,10 @@
listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, 1000));
// User sets brightness to 100
- mController.configure(AUTO_BRIGHTNESS_ENABLED, null /* configuration */,
- 0.5f /* brightness */, true /* userChangedBrightness */, 0 /* adjustment */,
- false /* userChanged */, DisplayPowerRequest.POLICY_BRIGHT);
+ mController.configure(AUTO_BRIGHTNESS_ENABLED, null /* configuration= */,
+ 0.5f /* brightness= */, true /* userChangedBrightness= */, 0 /* adjustment= */,
+ false /* userChanged= */, DisplayPowerRequest.POLICY_BRIGHT,
+ /* shouldResetShortTermModel= */ true);
// There should be a user data point added to the mapper.
verify(mBrightnessMappingStrategy, times(1)).addUserDataPoint(1000f, 0.5f);
@@ -323,9 +330,10 @@
verifyNoMoreInteractions(mBrightnessMappingStrategy);
// User sets idle brightness to 0.5
- mController.configure(AUTO_BRIGHTNESS_ENABLED, null /* configuration */,
- 0.5f /* brightness */, true /* userChangedBrightness */, 0 /* adjustment */,
- false /* userChanged */, DisplayPowerRequest.POLICY_BRIGHT);
+ mController.configure(AUTO_BRIGHTNESS_ENABLED, null /* configuration= */,
+ 0.5f /* brightness= */, true /* userChangedBrightness= */, 0 /* adjustment= */,
+ false /* userChanged= */, DisplayPowerRequest.POLICY_BRIGHT,
+ /* shouldResetShortTermModel= */ true);
// Ensure we use the correct mapping strategy
verify(mIdleBrightnessMappingStrategy, times(1)).addUserDataPoint(1000f, 0.5f);
@@ -481,17 +489,19 @@
final float throttledBrightness = 0.123f;
when(mBrightnessThrottler.getBrightnessCap()).thenReturn(throttledBrightness);
when(mBrightnessThrottler.isThrottled()).thenReturn(true);
- mController.configure(AUTO_BRIGHTNESS_ENABLED, null /* configuration */,
- BRIGHTNESS_MAX_FLOAT /* brightness */, false /* userChangedBrightness */,
- 0 /* adjustment */, false /* userChanged */, DisplayPowerRequest.POLICY_BRIGHT);
+ mController.configure(AUTO_BRIGHTNESS_ENABLED, null /* configuration= */,
+ BRIGHTNESS_MAX_FLOAT /* brightness= */, false /* userChangedBrightness= */,
+ 0 /* adjustment= */, false /* userChanged= */, DisplayPowerRequest.POLICY_BRIGHT,
+ /* shouldResetShortTermModel= */ true);
assertEquals(throttledBrightness, mController.getAutomaticScreenBrightness(), 0.0f);
// Remove throttling and notify ABC again
when(mBrightnessThrottler.getBrightnessCap()).thenReturn(BRIGHTNESS_MAX_FLOAT);
when(mBrightnessThrottler.isThrottled()).thenReturn(false);
- mController.configure(AUTO_BRIGHTNESS_ENABLED, null /* configuration */,
- BRIGHTNESS_MAX_FLOAT /* brightness */, false /* userChangedBrightness */,
- 0 /* adjustment */, false /* userChanged */, DisplayPowerRequest.POLICY_BRIGHT);
+ mController.configure(AUTO_BRIGHTNESS_ENABLED, null /* configuration= */,
+ BRIGHTNESS_MAX_FLOAT /* brightness= */, false /* userChangedBrightness= */,
+ 0 /* adjustment= */, false /* userChanged= */, DisplayPowerRequest.POLICY_BRIGHT,
+ /* shouldResetShortTermModel= */ true);
assertEquals(BRIGHTNESS_MAX_FLOAT, mController.getAutomaticScreenBrightness(), 0.0f);
}
@@ -582,4 +592,32 @@
assertEquals(lux, sensorValues[0], EPSILON);
assertEquals(mClock.now() - AMBIENT_LIGHT_HORIZON_LONG, sensorTimestamps[0]);
}
+
+ @Test
+ public void testResetShortTermModelWhenConfigChanges() {
+ when(mBrightnessMappingStrategy.isForIdleMode()).thenReturn(false);
+ when(mBrightnessMappingStrategy.setBrightnessConfiguration(any())).thenReturn(true);
+
+ mController.configure(AUTO_BRIGHTNESS_ENABLED, null /* configuration= */,
+ BRIGHTNESS_MAX_FLOAT /* brightness= */, false /* userChangedBrightness= */,
+ 0 /* adjustment= */, false /* userChanged= */, DisplayPowerRequest.POLICY_BRIGHT,
+ /* shouldResetShortTermModel= */ false);
+ verify(mBrightnessMappingStrategy, never()).clearUserDataPoints();
+
+ mController.configure(AUTO_BRIGHTNESS_ENABLED, null /* configuration= */,
+ BRIGHTNESS_MAX_FLOAT /* brightness= */, false /* userChangedBrightness= */,
+ 0 /* adjustment= */, false /* userChanged= */, DisplayPowerRequest.POLICY_BRIGHT,
+ /* shouldResetShortTermModel= */ true);
+ verify(mBrightnessMappingStrategy).clearUserDataPoints();
+ }
+
+ @Test
+ public void testUseProvidedShortTermModel() {
+ verify(mBrightnessMappingStrategy, never()).addUserDataPoint(anyFloat(), anyFloat());
+
+ float userLux = 1000;
+ float userBrightness = 0.3f;
+ setupController(mLightSensor, userLux, userBrightness);
+ verify(mBrightnessMappingStrategy).addUserDataPoint(userLux, userBrightness);
+ }
}
diff --git a/services/tests/servicestests/src/com/android/server/graphics/fonts/UpdatableFontDirTest.java b/services/tests/servicestests/src/com/android/server/graphics/fonts/UpdatableFontDirTest.java
index 68e5ebf..e9a7d85 100644
--- a/services/tests/servicestests/src/com/android/server/graphics/fonts/UpdatableFontDirTest.java
+++ b/services/tests/servicestests/src/com/android/server/graphics/fonts/UpdatableFontDirTest.java
@@ -210,7 +210,8 @@
assertThat(mUpdatableFontFilesDir.list()).hasLength(2);
assertNamedFamilyExists(dir.getSystemFontConfig(), "foobar");
assertThat(dir.getFontFamilyMap()).containsKey("foobar");
- FontConfig.FontFamily foobar = dir.getFontFamilyMap().get("foobar");
+ assertThat(dir.getFontFamilyMap().get("foobar").getFamilies().size()).isEqualTo(1);
+ FontConfig.FontFamily foobar = dir.getFontFamilyMap().get("foobar").getFamilies().get(0);
assertThat(foobar.getFontList()).hasSize(2);
assertThat(foobar.getFontList().get(0).getFile())
.isEqualTo(dir.getPostScriptMap().get("foo"));
@@ -326,10 +327,12 @@
new FontStyle(400, FontStyle.FONT_SLANT_UPRIGHT), 0, null, null);
FontConfig.FontFamily family = new FontConfig.FontFamily(
- Arrays.asList(fooFont, barFont), "sans-serif", null,
+ Arrays.asList(fooFont, barFont), null,
FontConfig.FontFamily.VARIANT_DEFAULT);
- return new FontConfig(Collections.singletonList(family),
- Collections.emptyList(), 0, 1);
+ return new FontConfig(Collections.emptyList(),
+ Collections.emptyList(),
+ Collections.singletonList(new FontConfig.NamedFamilyList(
+ Collections.singletonList(family), "sans-serif")), 0, 1);
};
UpdatableFontDir dirForPreparation = new UpdatableFontDir(
@@ -411,7 +414,8 @@
assertThat(dir.getPostScriptMap()).containsKey("foo");
assertThat(mParser.getRevision(dir.getPostScriptMap().get("foo"))).isEqualTo(1);
assertThat(dir.getFontFamilyMap()).containsKey("foobar");
- FontConfig.FontFamily foobar = dir.getFontFamilyMap().get("foobar");
+ assertThat(dir.getFontFamilyMap().get("foobar").getFamilies().size()).isEqualTo(1);
+ FontConfig.FontFamily foobar = dir.getFontFamilyMap().get("foobar").getFamilies().get(0);
assertThat(foobar.getFontList()).hasSize(1);
assertThat(foobar.getFontList().get(0).getFile())
.isEqualTo(dir.getPostScriptMap().get("foo"));
@@ -485,10 +489,12 @@
file, null, "bar", new FontStyle(400, FontStyle.FONT_SLANT_UPRIGHT),
0, null, null);
FontConfig.FontFamily family = new FontConfig.FontFamily(
- Collections.singletonList(font), "sans-serif", null,
- FontConfig.FontFamily.VARIANT_DEFAULT);
- return new FontConfig(Collections.singletonList(family),
- Collections.emptyList(), 0, 1);
+ Collections.singletonList(font), null, FontConfig.FontFamily.VARIANT_DEFAULT);
+ return new FontConfig(
+ Collections.emptyList(),
+ Collections.emptyList(),
+ Collections.singletonList(new FontConfig.NamedFamilyList(
+ Collections.singletonList(family), "sans-serif")), 0, 1);
});
dir.loadFontFileMap();
@@ -636,9 +642,10 @@
file, null, "test", new FontStyle(400, FontStyle.FONT_SLANT_UPRIGHT), 0, null,
null);
FontConfig.FontFamily family = new FontConfig.FontFamily(
- Collections.singletonList(font), "sans-serif", null,
- FontConfig.FontFamily.VARIANT_DEFAULT);
- return new FontConfig(Collections.singletonList(family), Collections.emptyList(), 0, 1);
+ Collections.singletonList(font), null, FontConfig.FontFamily.VARIANT_DEFAULT);
+ return new FontConfig(Collections.emptyList(), Collections.emptyList(),
+ Collections.singletonList(new FontConfig.NamedFamilyList(
+ Collections.singletonList(family), "sans-serif")), 0, 1);
});
dir.loadFontFileMap();
@@ -873,13 +880,14 @@
+ "</family>")));
assertThat(dir.getPostScriptMap()).containsKey("test");
assertThat(dir.getFontFamilyMap()).containsKey("test");
- FontConfig.FontFamily test = dir.getFontFamilyMap().get("test");
+ assertThat(dir.getFontFamilyMap().get("test").getFamilies().size()).isEqualTo(1);
+ FontConfig.FontFamily test = dir.getFontFamilyMap().get("test").getFamilies().get(0);
assertThat(test.getFontList()).hasSize(1);
assertThat(test.getFontList().get(0).getFile())
.isEqualTo(dir.getPostScriptMap().get("test"));
}
- @Test
+ @Test(expected = IllegalArgumentException.class)
public void addFontFamily_noName() throws Exception {
UpdatableFontDir dir = new UpdatableFontDir(
mUpdatableFontFilesDir, mParser, mFakeFsverityUtil,
@@ -891,12 +899,7 @@
newAddFontFamilyRequest("<family lang='en'>"
+ " <font>test.ttf</font>"
+ "</family>"));
- try {
- dir.update(requests);
- fail("Expect NullPointerException");
- } catch (NullPointerException e) {
- // Expect
- }
+ dir.update(requests);
}
@Test
@@ -955,17 +958,16 @@
dir.loadFontFileMap();
assertThat(dir.getSystemFontConfig().getFontFamilies()).isNotEmpty();
FontConfig.FontFamily firstFontFamily = dir.getSystemFontConfig().getFontFamilies().get(0);
- assertThat(firstFontFamily.getName()).isNotEmpty();
dir.update(Arrays.asList(
newFontUpdateRequest("test.ttf,1,test", GOOD_SIGNATURE),
- newAddFontFamilyRequest("<family name='" + firstFontFamily.getName() + "'>"
+ newAddFontFamilyRequest("<family name='sans-serif'>"
+ " <font>test.ttf</font>"
+ "</family>")));
FontConfig fontConfig = dir.getSystemFontConfig();
assertThat(dir.getSystemFontConfig().getFontFamilies()).isNotEmpty();
assertThat(fontConfig.getFontFamilies().get(0)).isEqualTo(firstFontFamily);
- FontConfig.FontFamily updated = getLastFamily(fontConfig, firstFontFamily.getName());
+ FontConfig.FontFamily updated = getLastFamily(fontConfig, "sans-serif");
assertThat(updated.getFontList()).hasSize(1);
assertThat(updated.getFontList().get(0).getFile())
.isEqualTo(dir.getPostScriptMap().get("test"));
@@ -1005,7 +1007,9 @@
mParser.setInput(is, "UTF-8");
mParser.nextTag();
- FontConfig.FontFamily fontFamily = FontListParser.readFamily(mParser, "", null, true);
+ FontConfig.NamedFamilyList namedFamilyList = FontListParser.readNamedFamily(
+ mParser, "", null, true);
+ FontConfig.FontFamily fontFamily = namedFamilyList.getFamilies().get(0);
List<FontUpdateRequest.Font> fonts = new ArrayList<>();
for (FontConfig.Font font : fontFamily.getFontList()) {
String name = font.getFile().getName();
@@ -1014,7 +1018,8 @@
psName, font.getStyle(), font.getTtcIndex(), font.getFontVariationSettings());
fonts.add(updateFont);
}
- FontUpdateRequest.Family family = new FontUpdateRequest.Family(fontFamily.getName(), fonts);
+ FontUpdateRequest.Family family = new FontUpdateRequest.Family(
+ namedFamilyList.getName(), fonts);
return new FontUpdateRequest(family);
}
@@ -1035,18 +1040,18 @@
// Returns the last family with the given name, which will be used for creating Typeface.
private static FontConfig.FontFamily getLastFamily(FontConfig fontConfig, String familyName) {
- List<FontConfig.FontFamily> fontFamilies = fontConfig.getFontFamilies();
- for (int i = fontFamilies.size() - 1; i >= 0; i--) {
- if (familyName.equals(fontFamilies.get(i).getName())) {
- return fontFamilies.get(i);
+ List<FontConfig.NamedFamilyList> namedFamilyLists = fontConfig.getNamedFamilyLists();
+ for (int i = namedFamilyLists.size() - 1; i >= 0; i--) {
+ if (familyName.equals(namedFamilyLists.get(i).getName())) {
+ return namedFamilyLists.get(i).getFamilies().get(0);
}
}
return null;
}
private static void assertNamedFamilyExists(FontConfig fontConfig, String familyName) {
- assertThat(fontConfig.getFontFamilies().stream()
- .map(FontConfig.FontFamily::getName)
+ assertThat(fontConfig.getNamedFamilyLists().stream()
+ .map(FontConfig.NamedFamilyList::getName)
.collect(Collectors.toSet())).contains(familyName);
}
}
diff --git a/services/tests/servicestests/src/com/android/server/inputmethod/AdditionalSubtypeUtilsTest.java b/services/tests/servicestests/src/com/android/server/inputmethod/AdditionalSubtypeUtilsTest.java
new file mode 100644
index 0000000..d82e6ab
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/inputmethod/AdditionalSubtypeUtilsTest.java
@@ -0,0 +1,84 @@
+/*
+ * Copyright (C) 2022 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.inputmethod;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+
+import android.icu.util.ULocale;
+import android.util.ArrayMap;
+import android.util.AtomicFile;
+import android.view.inputmethod.InputMethodInfo;
+import android.view.inputmethod.InputMethodSubtype;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.io.File;
+import java.util.List;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class AdditionalSubtypeUtilsTest {
+
+ @Test
+ public void testSaveAndLoad() throws Exception {
+ // Prepares the data to be saved.
+ InputMethodSubtype subtype1 = new InputMethodSubtype.InputMethodSubtypeBuilder()
+ .setSubtypeNameOverride("Subtype1")
+ .setLanguageTag("en-US")
+ .build();
+ InputMethodSubtype subtype2 = new InputMethodSubtype.InputMethodSubtypeBuilder()
+ .setSubtypeNameOverride("Subtype2")
+ .setLanguageTag("zh-CN")
+ .setPhysicalKeyboardHint(new ULocale("en_US"), "qwerty")
+ .build();
+ String fakeImeId = "fakeImeId";
+ ArrayMap<String, InputMethodInfo> methodMap = new ArrayMap<>();
+ methodMap.put(fakeImeId, new InputMethodInfo("", "", "", ""));
+ ArrayMap<String, List<InputMethodSubtype>> allSubtypes = new ArrayMap<>();
+ allSubtypes.put(fakeImeId, List.of(subtype1, subtype2));
+
+ // Save & load.
+ AtomicFile atomicFile = new AtomicFile(
+ new File(InstrumentationRegistry.getContext().getCacheDir(), "subtypes.xml"));
+ AdditionalSubtypeUtils.saveToFile(allSubtypes, methodMap, atomicFile);
+ ArrayMap<String, List<InputMethodSubtype>> loadedSubtypes = new ArrayMap<>();
+ AdditionalSubtypeUtils.loadFromFile(loadedSubtypes, atomicFile);
+
+ // Verifies the loaded data.
+ assertEquals(1, loadedSubtypes.size());
+ List<InputMethodSubtype> subtypes = loadedSubtypes.get(fakeImeId);
+ assertNotNull(subtypes);
+ assertEquals(2, subtypes.size());
+
+ verifySubtype(subtypes.get(0), subtype1);
+ verifySubtype(subtypes.get(1), subtype2);
+ }
+
+ private void verifySubtype(InputMethodSubtype subtype, InputMethodSubtype expectedSubtype) {
+ assertEquals(expectedSubtype.getLanguageTag(), subtype.getLanguageTag());
+ assertEquals(expectedSubtype.getPhysicalKeyboardHintLanguageTag(),
+ subtype.getPhysicalKeyboardHintLanguageTag());
+ assertEquals(expectedSubtype.getPhysicalKeyboardHintLayoutType(),
+ subtype.getPhysicalKeyboardHintLayoutType());
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/locales/LocaleManagerBackupRestoreTest.java b/services/tests/servicestests/src/com/android/server/locales/LocaleManagerBackupRestoreTest.java
index 4d42afa..1b8958b 100644
--- a/services/tests/servicestests/src/com/android/server/locales/LocaleManagerBackupRestoreTest.java
+++ b/services/tests/servicestests/src/com/android/server/locales/LocaleManagerBackupRestoreTest.java
@@ -158,7 +158,7 @@
mUserMonitor = mBackupHelper.getUserMonitor();
mPackageMonitor = new LocaleManagerServicePackageMonitor(mBackupHelper,
- systemAppUpdateTracker, appUpdateTracker);
+ systemAppUpdateTracker, appUpdateTracker, mMockLocaleManagerService);
setCurrentTimeMillis(DEFAULT_CREATION_TIME_MILLIS);
}
@@ -667,7 +667,7 @@
String pkgNameB = "com.android.myAppB";
setUpPackageNamesForSp(new ArraySet<>(Arrays.asList(pkgNameA, pkgNameB)));
- mPackageMonitor.onPackageRemoved(DEFAULT_PACKAGE_NAME, DEFAULT_UID);
+ mBackupHelper.onPackageRemoved(DEFAULT_PACKAGE_NAME, DEFAULT_UID);
verify(mMockSpEditor, times(0)).putStringSet(anyString(), any());
}
@@ -679,7 +679,7 @@
Set<String> pkgNames = new ArraySet<>(Arrays.asList(pkgNameA, pkgNameB));
setUpPackageNamesForSp(pkgNames);
- mPackageMonitor.onPackageRemoved(pkgNameA, DEFAULT_UID);
+ mBackupHelper.onPackageRemoved(pkgNameA, DEFAULT_UID);
pkgNames.remove(pkgNameA);
verify(mMockSpEditor, times(1)).putStringSet(
@@ -693,7 +693,7 @@
Set<String> pkgNames = new ArraySet<>(Arrays.asList(pkgNameA, pkgNameB));
setUpPackageNamesForSp(pkgNames);
- mPackageMonitor.onPackageDataCleared(pkgNameB, DEFAULT_UID);
+ mBackupHelper.onPackageDataCleared(pkgNameB, DEFAULT_UID);
pkgNames.remove(pkgNameB);
verify(mMockSpEditor, times(1)).putStringSet(
diff --git a/services/tests/servicestests/src/com/android/server/locales/SystemAppUpdateTrackerTest.java b/services/tests/servicestests/src/com/android/server/locales/SystemAppUpdateTrackerTest.java
index dc0740a..cbf555e 100644
--- a/services/tests/servicestests/src/com/android/server/locales/SystemAppUpdateTrackerTest.java
+++ b/services/tests/servicestests/src/com/android/server/locales/SystemAppUpdateTrackerTest.java
@@ -137,7 +137,7 @@
AppUpdateTracker appUpdateTracker = mock(AppUpdateTracker.class);
mPackageMonitor = new LocaleManagerServicePackageMonitor(mockLocaleManagerBackupHelper,
- mSystemAppUpdateTracker, appUpdateTracker);
+ mSystemAppUpdateTracker, appUpdateTracker, mLocaleManagerService);
}
@After
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsStrongAuthTest.java b/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsStrongAuthTest.java
index ec708ad..d251609 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsStrongAuthTest.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsStrongAuthTest.java
@@ -39,10 +39,12 @@
import android.app.AlarmManager;
import android.app.admin.DevicePolicyManager;
import android.content.Context;
+import android.platform.test.annotations.Presubmit;
import android.util.Log;
import androidx.test.InstrumentationRegistry;
import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
import com.android.server.locksettings.LockSettingsStrongAuth.NonStrongBiometricIdleTimeoutAlarmListener;
import com.android.server.locksettings.LockSettingsStrongAuth.NonStrongBiometricTimeoutAlarmListener;
@@ -50,10 +52,13 @@
import org.junit.Before;
import org.junit.Test;
+import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
@SmallTest
+@Presubmit
+@RunWith(AndroidJUnit4.class)
public class LockSettingsStrongAuthTest {
private static final String TAG = LockSettingsStrongAuthTest.class.getSimpleName();
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/LockscreenFrpTest.java b/services/tests/servicestests/src/com/android/server/locksettings/LockscreenFrpTest.java
index 10869da..2b49b8a 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/LockscreenFrpTest.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/LockscreenFrpTest.java
@@ -26,7 +26,9 @@
import android.app.PropertyInvalidatedCache;
import android.app.admin.DevicePolicyManager;
+import android.platform.test.annotations.Presubmit;
+import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
import com.android.internal.widget.VerifyCredentialResponse;
@@ -38,6 +40,8 @@
/** Test setting a lockscreen credential and then verify it under USER_FRP */
+@SmallTest
+@Presubmit
@RunWith(AndroidJUnit4.class)
public class LockscreenFrpTest extends BaseLockSettingsServiceTests {
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/MockSyntheticPasswordManager.java b/services/tests/servicestests/src/com/android/server/locksettings/MockSyntheticPasswordManager.java
index 8cb18a8..a40cb06 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/MockSyntheticPasswordManager.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/MockSyntheticPasswordManager.java
@@ -113,7 +113,7 @@
}
@Override
- protected IWeaver getWeaverService() throws RemoteException {
+ protected IWeaver getWeaverHidlService() throws RemoteException {
return mWeaverService;
}
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/RebootEscrowDataTest.java b/services/tests/servicestests/src/com/android/server/locksettings/RebootEscrowDataTest.java
index 2eedc32..771c3f1 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/RebootEscrowDataTest.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/RebootEscrowDataTest.java
@@ -19,6 +19,9 @@
import static org.hamcrest.CoreMatchers.is;
import static org.junit.Assert.assertThat;
+import android.platform.test.annotations.Presubmit;
+
+import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
import org.junit.Before;
@@ -34,6 +37,8 @@
/**
* atest FrameworksServicesTests:RebootEscrowDataTest
*/
+@SmallTest
+@Presubmit
@RunWith(AndroidJUnit4.class)
public class RebootEscrowDataTest {
private RebootEscrowKey mKey;
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/ResumeOnRebootServiceProviderTests.java b/services/tests/servicestests/src/com/android/server/locksettings/ResumeOnRebootServiceProviderTests.java
index f3a38e6..f94a99f 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/ResumeOnRebootServiceProviderTests.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/ResumeOnRebootServiceProviderTests.java
@@ -30,6 +30,7 @@
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.content.pm.ServiceInfo;
+import android.platform.test.annotations.Presubmit;
import android.service.resumeonreboot.ResumeOnRebootService;
import androidx.test.filters.SmallTest;
@@ -46,6 +47,7 @@
import java.util.ArrayList;
@SmallTest
+@Presubmit
@RunWith(JUnit4.class)
public class ResumeOnRebootServiceProviderTests {
diff --git a/services/tests/servicestests/src/com/android/server/logcat/LogcatManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/logcat/LogcatManagerServiceTest.java
index 2cd5314..771916e 100644
--- a/services/tests/servicestests/src/com/android/server/logcat/LogcatManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/logcat/LogcatManagerServiceTest.java
@@ -22,22 +22,19 @@
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
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 org.mockito.Mockito.when;
import android.app.ActivityManager;
import android.app.ActivityManagerInternal;
-import android.content.ContextWrapper;
+import android.content.Context;
import android.content.pm.PackageManager;
import android.os.ILogd;
import android.os.Looper;
import android.os.UserHandle;
import android.os.test.TestLooper;
-import androidx.test.core.app.ApplicationProvider;
-
import com.android.server.LocalServices;
import com.android.server.logcat.LogcatManagerService.Injector;
import com.android.server.testutils.OffsettableClock;
@@ -70,6 +67,8 @@
private static final int FD2 = 11;
@Mock
+ private Context mContextSpy;
+ @Mock
private ActivityManagerInternal mActivityManagerInternalMock;
@Mock
private PackageManager mPackageManagerMock;
@@ -78,7 +77,6 @@
private LogcatManagerService mService;
private LogcatManagerService.LogAccessDialogCallback mDialogCallback;
- private ContextWrapper mContextSpy;
private OffsettableClock mClock;
private TestLooper mTestLooper;
@@ -89,7 +87,6 @@
when(mActivityManagerInternalMock.getInstrumentationSourceUid(anyInt()))
.thenReturn(INVALID_UID);
- mContextSpy = spy(new ContextWrapper(ApplicationProvider.getApplicationContext()));
mClock = new OffsettableClock.Stopped();
mTestLooper = new TestLooper(mClock::now);
when(mContextSpy.getPackageManager()).thenReturn(mPackageManagerMock);
diff --git a/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java b/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java
index a95b811..ab7cf45 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java
@@ -89,7 +89,6 @@
import org.mockito.ArgumentCaptor;
import java.util.ArrayList;
-import java.util.Arrays;
import java.util.Objects;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
@@ -129,10 +128,10 @@
final ActivityRecord closing = createActivityRecord(oldTask);
final ActivityRecord opening = createActivityRecord(newTask);
// Start states.
- changes.put(newTask, new Transition.ChangeInfo(false /* vis */, true /* exChg */));
- changes.put(oldTask, new Transition.ChangeInfo(true /* vis */, true /* exChg */));
- changes.put(opening, new Transition.ChangeInfo(false /* vis */, true /* exChg */));
- changes.put(closing, new Transition.ChangeInfo(true /* vis */, true /* exChg */));
+ changes.put(newTask, new Transition.ChangeInfo(newTask, false /* vis */, true /* exChg */));
+ changes.put(oldTask, new Transition.ChangeInfo(oldTask, true /* vis */, true /* exChg */));
+ changes.put(opening, new Transition.ChangeInfo(opening, false /* vis */, true /* exChg */));
+ changes.put(closing, new Transition.ChangeInfo(closing, true /* vis */, true /* exChg */));
fillChangeMap(changes, newTask);
// End states.
closing.setVisibleRequested(false);
@@ -144,9 +143,9 @@
// Check basic both tasks participating
participants.add(oldTask);
participants.add(newTask);
- ArrayList<WindowContainer> targets = Transition.calculateTargets(participants, changes);
- TransitionInfo info = Transition.calculateTransitionInfo(transit, flags, targets, changes,
- mMockT);
+ ArrayList<Transition.ChangeInfo> targets =
+ Transition.calculateTargets(participants, changes);
+ TransitionInfo info = Transition.calculateTransitionInfo(transit, flags, targets, mMockT);
assertEquals(2, info.getChanges().size());
assertEquals(transit, info.getType());
@@ -154,7 +153,7 @@
participants.add(opening);
participants.add(closing);
targets = Transition.calculateTargets(participants, changes);
- info = Transition.calculateTransitionInfo(transit, flags, targets, changes, mMockT);
+ info = Transition.calculateTransitionInfo(transit, flags, targets, mMockT);
assertEquals(2, info.getChanges().size());
assertNotNull(info.getChange(newTask.mRemoteToken.toWindowContainerToken()));
assertNotNull(info.getChange(oldTask.mRemoteToken.toWindowContainerToken()));
@@ -162,7 +161,7 @@
// Check combined prune and promote
participants.remove(newTask);
targets = Transition.calculateTargets(participants, changes);
- info = Transition.calculateTransitionInfo(transit, flags, targets, changes, mMockT);
+ info = Transition.calculateTransitionInfo(transit, flags, targets, mMockT);
assertEquals(2, info.getChanges().size());
assertNotNull(info.getChange(newTask.mRemoteToken.toWindowContainerToken()));
assertNotNull(info.getChange(oldTask.mRemoteToken.toWindowContainerToken()));
@@ -170,7 +169,7 @@
// Check multi promote
participants.remove(oldTask);
targets = Transition.calculateTargets(participants, changes);
- info = Transition.calculateTransitionInfo(transit, flags, targets, changes, mMockT);
+ info = Transition.calculateTransitionInfo(transit, flags, targets, mMockT);
assertEquals(2, info.getChanges().size());
assertNotNull(info.getChange(newTask.mRemoteToken.toWindowContainerToken()));
assertNotNull(info.getChange(oldTask.mRemoteToken.toWindowContainerToken()));
@@ -190,13 +189,16 @@
final ActivityRecord opening = createActivityRecord(newNestedTask);
final ActivityRecord opening2 = createActivityRecord(newNestedTask2);
// Start states.
- changes.put(newTask, new Transition.ChangeInfo(false /* vis */, true /* exChg */));
- changes.put(newNestedTask, new Transition.ChangeInfo(false /* vis */, true /* exChg */));
- changes.put(newNestedTask2, new Transition.ChangeInfo(false /* vis */, true /* exChg */));
- changes.put(oldTask, new Transition.ChangeInfo(true /* vis */, true /* exChg */));
- changes.put(opening, new Transition.ChangeInfo(false /* vis */, true /* exChg */));
- changes.put(opening2, new Transition.ChangeInfo(false /* vis */, true /* exChg */));
- changes.put(closing, new Transition.ChangeInfo(true /* vis */, true /* exChg */));
+ changes.put(newTask, new Transition.ChangeInfo(newTask, false /* vis */, true /* exChg */));
+ changes.put(newNestedTask,
+ new Transition.ChangeInfo(newNestedTask, false /* vis */, true /* exChg */));
+ changes.put(newNestedTask2,
+ new Transition.ChangeInfo(newNestedTask2, false /* vis */, true /* exChg */));
+ changes.put(oldTask, new Transition.ChangeInfo(oldTask, true /* vis */, true /* exChg */));
+ changes.put(opening, new Transition.ChangeInfo(opening, false /* vis */, true /* exChg */));
+ changes.put(opening2,
+ new Transition.ChangeInfo(opening2, false /* vis */, true /* exChg */));
+ changes.put(closing, new Transition.ChangeInfo(closing, true /* vis */, true /* exChg */));
fillChangeMap(changes, newTask);
// End states.
closing.setVisibleRequested(false);
@@ -210,9 +212,9 @@
participants.add(oldTask);
participants.add(opening);
participants.add(opening2);
- ArrayList<WindowContainer> targets = Transition.calculateTargets(participants, changes);
- TransitionInfo info = Transition.calculateTransitionInfo(transit, flags, targets, changes,
- mMockT);
+ ArrayList<Transition.ChangeInfo> targets =
+ Transition.calculateTargets(participants, changes);
+ TransitionInfo info = Transition.calculateTransitionInfo(transit, flags, targets, mMockT);
assertEquals(2, info.getChanges().size());
assertEquals(transit, info.getType());
assertNotNull(info.getChange(newTask.mRemoteToken.toWindowContainerToken()));
@@ -221,7 +223,7 @@
// Check that unchanging but visible descendant of sibling prevents promotion
participants.remove(opening2);
targets = Transition.calculateTargets(participants, changes);
- info = Transition.calculateTransitionInfo(transit, flags, targets, changes, mMockT);
+ info = Transition.calculateTransitionInfo(transit, flags, targets, mMockT);
assertEquals(2, info.getChanges().size());
assertNotNull(info.getChange(newNestedTask.mRemoteToken.toWindowContainerToken()));
assertNotNull(info.getChange(oldTask.mRemoteToken.toWindowContainerToken()));
@@ -241,12 +243,16 @@
final ActivityRecord showing = createActivityRecord(showNestedTask);
final ActivityRecord showing2 = createActivityRecord(showTask2);
// Start states.
- changes.put(showTask, new Transition.ChangeInfo(false /* vis */, true /* exChg */));
- changes.put(showNestedTask, new Transition.ChangeInfo(false /* vis */, true /* exChg */));
- changes.put(showTask2, new Transition.ChangeInfo(false /* vis */, true /* exChg */));
- changes.put(tda, new Transition.ChangeInfo(false /* vis */, true /* exChg */));
- changes.put(showing, new Transition.ChangeInfo(false /* vis */, true /* exChg */));
- changes.put(showing2, new Transition.ChangeInfo(false /* vis */, true /* exChg */));
+ changes.put(showTask,
+ new Transition.ChangeInfo(showTask, false /* vis */, true /* exChg */));
+ changes.put(showNestedTask,
+ new Transition.ChangeInfo(showNestedTask, false /* vis */, true /* exChg */));
+ changes.put(showTask2,
+ new Transition.ChangeInfo(showTask2, false /* vis */, true /* exChg */));
+ changes.put(tda, new Transition.ChangeInfo(tda, false /* vis */, true /* exChg */));
+ changes.put(showing, new Transition.ChangeInfo(showing, false /* vis */, true /* exChg */));
+ changes.put(showing2,
+ new Transition.ChangeInfo(showing2, false /* vis */, true /* exChg */));
fillChangeMap(changes, tda);
// End states.
@@ -259,9 +265,9 @@
// Check promotion to DisplayArea
participants.add(showing);
participants.add(showing2);
- ArrayList<WindowContainer> targets = Transition.calculateTargets(participants, changes);
- TransitionInfo info = Transition.calculateTransitionInfo(transit, flags, targets, changes,
- mMockT);
+ ArrayList<Transition.ChangeInfo> targets =
+ Transition.calculateTargets(participants, changes);
+ TransitionInfo info = Transition.calculateTransitionInfo(transit, flags, targets, mMockT);
assertEquals(1, info.getChanges().size());
assertEquals(transit, info.getType());
assertNotNull(info.getChange(tda.mRemoteToken.toWindowContainerToken()));
@@ -269,14 +275,14 @@
// Check that organized tasks get reported even if not top
makeTaskOrganized(showTask);
targets = Transition.calculateTargets(participants, changes);
- info = Transition.calculateTransitionInfo(transit, flags, targets, changes, mMockT);
+ info = Transition.calculateTransitionInfo(transit, flags, targets, mMockT);
assertEquals(2, info.getChanges().size());
assertNotNull(info.getChange(tda.mRemoteToken.toWindowContainerToken()));
assertNotNull(info.getChange(showTask.mRemoteToken.toWindowContainerToken()));
// Even if DisplayArea explicitly participating
participants.add(tda);
targets = Transition.calculateTargets(participants, changes);
- info = Transition.calculateTransitionInfo(transit, flags, targets, changes, mMockT);
+ info = Transition.calculateTransitionInfo(transit, flags, targets, mMockT);
assertEquals(2, info.getChanges().size());
}
@@ -297,10 +303,9 @@
opening.setVisibleRequested(true);
closing.setVisibleRequested(false);
- ArrayList<WindowContainer> targets = Transition.calculateTargets(
+ ArrayList<Transition.ChangeInfo> targets = Transition.calculateTargets(
transition.mParticipants, transition.mChanges);
- TransitionInfo info = Transition.calculateTransitionInfo(
- 0, 0, targets, transition.mChanges, mMockT);
+ TransitionInfo info = Transition.calculateTransitionInfo(0, 0, targets, mMockT);
assertEquals(2, info.getChanges().size());
// There was an existence change on open, so it should be OPEN rather than SHOW
assertEquals(TRANSIT_OPEN,
@@ -334,10 +339,9 @@
tasks[i].getTopMostActivity().setVisibleRequested((i % 2) != 0);
}
- ArrayList<WindowContainer> targets = Transition.calculateTargets(
+ ArrayList<Transition.ChangeInfo> targets = Transition.calculateTargets(
transition.mParticipants, transition.mChanges);
- TransitionInfo info = Transition.calculateTransitionInfo(
- 0, 0, targets, transition.mChanges, mMockT);
+ TransitionInfo info = Transition.calculateTransitionInfo(0, 0, targets, mMockT);
assertEquals(taskCount, info.getChanges().size());
// verify order is top-to-bottem
for (int i = 0; i < taskCount; ++i) {
@@ -384,10 +388,9 @@
tasks[i].getTopMostActivity().setVisibleRequested((i % 2) != 0);
}
- ArrayList<WindowContainer> targets = Transition.calculateTargets(
+ ArrayList<Transition.ChangeInfo> targets = Transition.calculateTargets(
transition.mParticipants, transition.mChanges);
- TransitionInfo info = Transition.calculateTransitionInfo(
- 0, 0, targets, transition.mChanges, mMockT);
+ TransitionInfo info = Transition.calculateTransitionInfo(0, 0, targets, mMockT);
// verify that wallpaper is at bottom
assertEquals(taskCount + 1, info.getChanges().size());
// The wallpaper is not organized, so it won't have a token; however, it will be marked
@@ -410,11 +413,13 @@
final ActivityRecord hiding = createActivityRecord(topTask);
final ActivityRecord closing = createActivityRecord(topTask);
// Start states.
- changes.put(topTask, new Transition.ChangeInfo(true /* vis */, false /* exChg */));
- changes.put(belowTask, new Transition.ChangeInfo(false /* vis */, false /* exChg */));
- changes.put(showing, new Transition.ChangeInfo(false /* vis */, false /* exChg */));
- changes.put(hiding, new Transition.ChangeInfo(true /* vis */, false /* exChg */));
- changes.put(closing, new Transition.ChangeInfo(true /* vis */, true /* exChg */));
+ changes.put(topTask, new Transition.ChangeInfo(topTask, true /* vis */, false /* exChg */));
+ changes.put(belowTask,
+ new Transition.ChangeInfo(belowTask, false /* vis */, false /* exChg */));
+ changes.put(showing,
+ new Transition.ChangeInfo(showing, false /* vis */, false /* exChg */));
+ changes.put(hiding, new Transition.ChangeInfo(hiding, true /* vis */, false /* exChg */));
+ changes.put(closing, new Transition.ChangeInfo(closing, true /* vis */, true /* exChg */));
fillChangeMap(changes, topTask);
// End states.
showing.setVisibleRequested(true);
@@ -424,10 +429,11 @@
participants.add(belowTask);
participants.add(hiding);
participants.add(closing);
- ArrayList<WindowContainer> targets = Transition.calculateTargets(participants, changes);
+ ArrayList<Transition.ChangeInfo> targets =
+ Transition.calculateTargets(participants, changes);
assertEquals(2, targets.size());
- assertTrue(targets.contains(belowTask));
- assertTrue(targets.contains(topTask));
+ assertTrue(Transition.containsChangeFor(belowTask, targets));
+ assertTrue(Transition.containsChangeFor(topTask, targets));
}
@Test
@@ -442,11 +448,14 @@
final ActivityRecord opening = createActivityRecord(topTask);
final ActivityRecord closing = createActivityRecord(belowTask);
// Start states.
- changes.put(topTask, new Transition.ChangeInfo(false /* vis */, false /* exChg */));
- changes.put(belowTask, new Transition.ChangeInfo(true /* vis */, false /* exChg */));
- changes.put(showing, new Transition.ChangeInfo(false /* vis */, false /* exChg */));
- changes.put(opening, new Transition.ChangeInfo(false /* vis */, true /* exChg */));
- changes.put(closing, new Transition.ChangeInfo(true /* vis */, false /* exChg */));
+ changes.put(topTask,
+ new Transition.ChangeInfo(topTask, false /* vis */, false /* exChg */));
+ changes.put(belowTask,
+ new Transition.ChangeInfo(belowTask, true /* vis */, false /* exChg */));
+ changes.put(showing,
+ new Transition.ChangeInfo(showing, false /* vis */, false /* exChg */));
+ changes.put(opening, new Transition.ChangeInfo(opening, false /* vis */, true /* exChg */));
+ changes.put(closing, new Transition.ChangeInfo(closing, true /* vis */, false /* exChg */));
fillChangeMap(changes, topTask);
// End states.
showing.setVisibleRequested(true);
@@ -456,10 +465,11 @@
participants.add(belowTask);
participants.add(showing);
participants.add(opening);
- ArrayList<WindowContainer> targets = Transition.calculateTargets(participants, changes);
+ ArrayList<Transition.ChangeInfo> targets =
+ Transition.calculateTargets(participants, changes);
assertEquals(2, targets.size());
- assertTrue(targets.contains(belowTask));
- assertTrue(targets.contains(topTask));
+ assertTrue(Transition.containsChangeFor(belowTask, targets));
+ assertTrue(Transition.containsChangeFor(topTask, targets));
}
@Test
@@ -473,10 +483,10 @@
final ActivityRecord closing = createActivityRecord(oldTask);
final ActivityRecord opening = createActivityRecord(newTask);
// Start states.
- changes.put(newTask, new Transition.ChangeInfo(false /* vis */, true /* exChg */));
- changes.put(oldTask, new Transition.ChangeInfo(true /* vis */, true /* exChg */));
- changes.put(opening, new Transition.ChangeInfo(false /* vis */, true /* exChg */));
- changes.put(closing, new Transition.ChangeInfo(true /* vis */, true /* exChg */));
+ changes.put(newTask, new Transition.ChangeInfo(newTask, false /* vis */, true /* exChg */));
+ changes.put(oldTask, new Transition.ChangeInfo(oldTask, true /* vis */, true /* exChg */));
+ changes.put(opening, new Transition.ChangeInfo(opening, false /* vis */, true /* exChg */));
+ changes.put(closing, new Transition.ChangeInfo(closing, true /* vis */, true /* exChg */));
transition.setNoAnimation(opening);
fillChangeMap(changes, newTask);
// End states.
@@ -491,19 +501,20 @@
participants.add(newTask);
participants.add(opening);
participants.add(closing);
- ArrayList<WindowContainer> targets = Transition.calculateTargets(participants, changes);
- TransitionInfo info = Transition.calculateTransitionInfo(transit, flags, targets, changes,
- mMockT);
+ ArrayList<Transition.ChangeInfo> targets =
+ Transition.calculateTargets(participants, changes);
+ TransitionInfo info = Transition.calculateTransitionInfo(transit, flags, targets, mMockT);
assertNotNull(info.getChange(newTask.mRemoteToken.toWindowContainerToken()));
assertTrue(info.getChange(newTask.mRemoteToken.toWindowContainerToken())
.hasFlags(TransitionInfo.FLAG_NO_ANIMATION));
// Check that no-animation flag is NOT promoted if at-least on child *is* animated
final ActivityRecord opening2 = createActivityRecord(newTask);
- changes.put(opening2, new Transition.ChangeInfo(false /* vis */, true /* exChg */));
+ changes.put(opening2,
+ new Transition.ChangeInfo(opening2, false /* vis */, true /* exChg */));
participants.add(opening2);
targets = Transition.calculateTargets(participants, changes);
- info = Transition.calculateTransitionInfo(transit, flags, targets, changes, mMockT);
+ info = Transition.calculateTransitionInfo(transit, flags, targets, mMockT);
assertNotNull(info.getChange(newTask.mRemoteToken.toWindowContainerToken()));
assertFalse(info.getChange(newTask.mRemoteToken.toWindowContainerToken())
.hasFlags(TransitionInfo.FLAG_NO_ANIMATION));
@@ -531,10 +542,9 @@
mDisplayContent.getWindowConfiguration().setRotation(
(mDisplayContent.getWindowConfiguration().getRotation() + 1) % 4);
- ArrayList<WindowContainer> targets = Transition.calculateTargets(
+ ArrayList<Transition.ChangeInfo> targets = Transition.calculateTargets(
transition.mParticipants, transition.mChanges);
- TransitionInfo info = Transition.calculateTransitionInfo(
- 0, 0, targets, transition.mChanges, mMockT);
+ TransitionInfo info = Transition.calculateTransitionInfo(0, 0, targets, mMockT);
// The wallpaper is not organized, so it won't have a token; however, it will be marked
// as IS_WALLPAPER
assertEquals(FLAG_IS_WALLPAPER, info.getChanges().get(0).getFlags());
@@ -596,10 +606,12 @@
wc.getWindowConfiguration().setRotation(newRotation);
}
- final ArrayList<WindowContainer> targets = Transition.calculateTargets(
+ final ArrayList<Transition.ChangeInfo> targets = Transition.calculateTargets(
transition.mParticipants, transition.mChanges);
// Especially the activities must be in the targets.
- assertTrue(targets.containsAll(Arrays.asList(wcs)));
+ for (WindowContainer<?> wc : wcs) {
+ assertTrue(Transition.containsChangeFor(wc, targets));
+ }
}
@Test
@@ -622,15 +634,22 @@
openInChangeTask);
// Start states.
- changes.put(openTask, new Transition.ChangeInfo(false /* vis */, true /* exChg */));
- changes.put(changeTask, new Transition.ChangeInfo(true /* vis */, false /* exChg */));
- changes.put(openInOpenTask, new Transition.ChangeInfo(false /* vis */, true /* exChg */));
- changes.put(openInChangeTask, new Transition.ChangeInfo(false /* vis */, true /* exChg */));
+ changes.put(openTask,
+ new Transition.ChangeInfo(openTask, false /* vis */, true /* exChg */));
+ changes.put(changeTask,
+ new Transition.ChangeInfo(changeTask, true /* vis */, false /* exChg */));
+ changes.put(openInOpenTask,
+ new Transition.ChangeInfo(openInOpenTask, false /* vis */, true /* exChg */));
+ changes.put(openInChangeTask,
+ new Transition.ChangeInfo(openInChangeTask, false /* vis */, true /* exChg */));
changes.put(changeInChangeTask,
- new Transition.ChangeInfo(true /* vis */, false /* exChg */));
- changes.put(openInOpen, new Transition.ChangeInfo(false /* vis */, true /* exChg */));
- changes.put(openInChange, new Transition.ChangeInfo(false /* vis */, true /* exChg */));
- changes.put(changeInChange, new Transition.ChangeInfo(true /* vis */, false /* exChg */));
+ new Transition.ChangeInfo(changeInChangeTask, true /* vis */, false /* exChg */));
+ changes.put(openInOpen,
+ new Transition.ChangeInfo(openInOpen, false /* vis */, true /* exChg */));
+ changes.put(openInChange,
+ new Transition.ChangeInfo(openInChange, false /* vis */, true /* exChg */));
+ changes.put(changeInChange,
+ new Transition.ChangeInfo(changeInChange, true /* vis */, false /* exChg */));
fillChangeMap(changes, openTask);
// End states.
changeInChange.setVisibleRequested(true);
@@ -646,10 +665,9 @@
participants.add(openInChange);
// Explicitly add changeTask (to test independence with parents)
participants.add(changeTask);
- final ArrayList<WindowContainer> targets =
+ final ArrayList<Transition.ChangeInfo> targets =
Transition.calculateTargets(participants, changes);
- TransitionInfo info = Transition.calculateTransitionInfo(transit, flags, targets, changes,
- mMockT);
+ TransitionInfo info = Transition.calculateTransitionInfo(transit, flags, targets, mMockT);
// Root changes should always be considered independent
assertTrue(isIndependent(
info.getChange(openTask.mRemoteToken.toWindowContainerToken()), info));
@@ -685,10 +703,10 @@
final ActivityRecord opening = createActivityRecord(newTask);
opening.setOccludesParent(false);
// Start states.
- changes.put(newTask, new Transition.ChangeInfo(false /* vis */, true /* exChg */));
- changes.put(oldTask, new Transition.ChangeInfo(true /* vis */, false /* exChg */));
- changes.put(opening, new Transition.ChangeInfo(false /* vis */, true /* exChg */));
- changes.put(closing, new Transition.ChangeInfo(true /* vis */, false /* exChg */));
+ changes.put(newTask, new Transition.ChangeInfo(newTask, false /* vis */, true /* exChg */));
+ changes.put(oldTask, new Transition.ChangeInfo(oldTask, true /* vis */, false /* exChg */));
+ changes.put(opening, new Transition.ChangeInfo(opening, false /* vis */, true /* exChg */));
+ changes.put(closing, new Transition.ChangeInfo(closing, true /* vis */, false /* exChg */));
fillChangeMap(changes, newTask);
// End states.
closing.setVisibleRequested(true);
@@ -700,9 +718,9 @@
// Check basic both tasks participating
participants.add(oldTask);
participants.add(newTask);
- ArrayList<WindowContainer> targets = Transition.calculateTargets(participants, changes);
- TransitionInfo info = Transition.calculateTransitionInfo(transit, flags, targets, changes,
- mMockT);
+ ArrayList<Transition.ChangeInfo> targets =
+ Transition.calculateTargets(participants, changes);
+ TransitionInfo info = Transition.calculateTransitionInfo(transit, flags, targets, mMockT);
assertEquals(2, info.getChanges().size());
assertEquals(transit, info.getType());
@@ -726,10 +744,10 @@
final ActivityRecord opening = createActivityRecord(newTask);
opening.setOccludesParent(false);
// Start states.
- changes.put(newTask, new Transition.ChangeInfo(false /* vis */, true /* exChg */));
- changes.put(oldTask, new Transition.ChangeInfo(true /* vis */, false /* exChg */));
- changes.put(opening, new Transition.ChangeInfo(false /* vis */, true /* exChg */));
- changes.put(closing, new Transition.ChangeInfo(true /* vis */, false /* exChg */));
+ changes.put(newTask, new Transition.ChangeInfo(newTask, false /* vis */, true /* exChg */));
+ changes.put(oldTask, new Transition.ChangeInfo(oldTask, true /* vis */, false /* exChg */));
+ changes.put(opening, new Transition.ChangeInfo(opening, false /* vis */, true /* exChg */));
+ changes.put(closing, new Transition.ChangeInfo(closing, true /* vis */, false /* exChg */));
fillChangeMap(changes, newTask);
// End states.
closing.setVisibleRequested(true);
@@ -741,9 +759,9 @@
// Check basic both tasks participating
participants.add(oldTask);
participants.add(newTask);
- ArrayList<WindowContainer> targets = Transition.calculateTargets(participants, changes);
- TransitionInfo info = Transition.calculateTransitionInfo(transit, flags, targets, changes,
- mMockT);
+ ArrayList<Transition.ChangeInfo> targets =
+ Transition.calculateTargets(participants, changes);
+ TransitionInfo info = Transition.calculateTransitionInfo(transit, flags, targets, mMockT);
assertEquals(2, info.getChanges().size());
assertEquals(transit, info.getType());
@@ -785,7 +803,7 @@
final int flags = 0;
final TransitionInfo info = Transition.calculateTransitionInfo(transition.mType, flags,
Transition.calculateTargets(transition.mParticipants, transition.mChanges),
- transition.mChanges, mMockT);
+ mMockT);
transition.abort();
return info.getChanges().get(0);
};
@@ -1210,18 +1228,20 @@
doReturn(true).when(activity1).hasStartingWindow();
// Start states.
- changes.put(activity0, new Transition.ChangeInfo(true /* vis */, false /* exChg */));
- changes.put(activity1, new Transition.ChangeInfo(false /* vis */, false /* exChg */));
+ changes.put(activity0,
+ new Transition.ChangeInfo(activity0, true /* vis */, false /* exChg */));
+ changes.put(activity1,
+ new Transition.ChangeInfo(activity1, false /* vis */, false /* exChg */));
// End states.
activity0.setVisibleRequested(false);
activity1.setVisibleRequested(true);
participants.add(activity0);
participants.add(activity1);
- final ArrayList<WindowContainer> targets = Transition.calculateTargets(
+ final ArrayList<Transition.ChangeInfo> targets = Transition.calculateTargets(
participants, changes);
final TransitionInfo info = Transition.calculateTransitionInfo(
- transition.mType, 0 /* flags */, targets, changes, mMockT);
+ transition.mType, 0 /* flags */, targets, mMockT);
// All windows in the Task should have FLAG_IS_BEHIND_STARTING_WINDOW because the starting
// window should cover the whole Task.
@@ -1251,11 +1271,14 @@
final ActivityRecord closingActivity = embeddedTf.getBottomMostActivity();
final ActivityRecord openingActivity = embeddedTf.getTopMostActivity();
// Start states.
- changes.put(embeddedTf, new Transition.ChangeInfo(true /* vis */, false /* exChg */));
- changes.put(closingActivity, new Transition.ChangeInfo(true /* vis */, false /* exChg */));
- changes.put(openingActivity, new Transition.ChangeInfo(false /* vis */, true /* exChg */));
- changes.put(nonEmbeddedActivity, new Transition.ChangeInfo(true /* vis */,
- false /* exChg */));
+ changes.put(embeddedTf,
+ new Transition.ChangeInfo(embeddedTf, true /* vis */, false /* exChg */));
+ changes.put(closingActivity,
+ new Transition.ChangeInfo(closingActivity, true /* vis */, false /* exChg */));
+ changes.put(openingActivity,
+ new Transition.ChangeInfo(openingActivity, false /* vis */, true /* exChg */));
+ changes.put(nonEmbeddedActivity, new Transition.ChangeInfo(nonEmbeddedActivity,
+ true /* vis */, false /* exChg */));
// End states.
closingActivity.setVisibleRequested(false);
openingActivity.setVisibleRequested(true);
@@ -1264,10 +1287,10 @@
participants.add(closingActivity);
participants.add(openingActivity);
participants.add(nonEmbeddedActivity);
- final ArrayList<WindowContainer> targets = Transition.calculateTargets(
+ final ArrayList<Transition.ChangeInfo> targets = Transition.calculateTargets(
participants, changes);
final TransitionInfo info = Transition.calculateTransitionInfo(
- transition.mType, 0 /* flags */, targets, changes, mMockT);
+ transition.mType, 0 /* flags */, targets, mMockT);
// All windows in the Task should have FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY because the Task
// contains embedded activity.
@@ -1297,10 +1320,11 @@
.build();
final ActivityRecord embeddedActivity = embeddedTf.getTopMostActivity();
// Start states.
- changes.put(task, new Transition.ChangeInfo(true /* vis */, false /* exChg */));
- changes.put(nonEmbeddedActivity, new Transition.ChangeInfo(true /* vis */,
- false /* exChg */));
- changes.put(embeddedTf, new Transition.ChangeInfo(false /* vis */, true /* exChg */));
+ changes.put(task, new Transition.ChangeInfo(task, true /* vis */, false /* exChg */));
+ changes.put(nonEmbeddedActivity,
+ new Transition.ChangeInfo(nonEmbeddedActivity, true /* vis */, false /* exChg */));
+ changes.put(embeddedTf,
+ new Transition.ChangeInfo(embeddedTf, false /* vis */, true /* exChg */));
// End states.
nonEmbeddedActivity.setVisibleRequested(false);
embeddedActivity.setVisibleRequested(true);
@@ -1308,10 +1332,10 @@
participants.add(nonEmbeddedActivity);
participants.add(embeddedTf);
- final ArrayList<WindowContainer> targets = Transition.calculateTargets(
+ final ArrayList<Transition.ChangeInfo> targets = Transition.calculateTargets(
participants, changes);
final TransitionInfo info = Transition.calculateTransitionInfo(
- transition.mType, 0 /* flags */, targets, changes, mMockT);
+ transition.mType, 0 /* flags */, targets, mMockT);
// The embedded with bounds overridden should not have the flag.
assertEquals(2, info.getChanges().size());
@@ -1339,10 +1363,10 @@
activity.setVisibleRequested(true);
participants.add(activity);
- final ArrayList<WindowContainer> targets = Transition.calculateTargets(
+ final ArrayList<Transition.ChangeInfo> targets = Transition.calculateTargets(
participants, changes);
final TransitionInfo info = Transition.calculateTransitionInfo(
- transition.mType, 0 /* flags */, targets, changes, mMockT);
+ transition.mType, 0 /* flags */, targets, mMockT);
// Opening activity that is filling Task after transition should have the flag.
assertEquals(1, info.getChanges().size());
@@ -1367,10 +1391,10 @@
activity.setVisibleRequested(false);
participants.add(activity);
- final ArrayList<WindowContainer> targets = Transition.calculateTargets(
+ final ArrayList<Transition.ChangeInfo> targets = Transition.calculateTargets(
participants, changes);
final TransitionInfo info = Transition.calculateTransitionInfo(
- transition.mType, 0 /* flags */, targets, changes, mMockT);
+ transition.mType, 0 /* flags */, targets, mMockT);
// Closing activity that is filling Task before transition should have the flag.
assertEquals(1, info.getChanges().size());
@@ -1395,10 +1419,10 @@
activity.setVisibleRequested(false);
participants.add(activity);
- final ArrayList<WindowContainer> targets = Transition.calculateTargets(
+ final ArrayList<Transition.ChangeInfo> targets = Transition.calculateTargets(
participants, changes);
final TransitionInfo info = Transition.calculateTransitionInfo(
- transition.mType, 0 /* flags */, targets, changes, mMockT);
+ transition.mType, 0 /* flags */, targets, mMockT);
// Change contains last parent info.
assertEquals(1, info.getChanges().size());
@@ -1433,10 +1457,10 @@
activity.reparent(embeddedTf, POSITION_TOP);
// Verify that both activity and TaskFragment are included.
- final ArrayList<WindowContainer> targets = Transition.calculateTargets(
+ final ArrayList<Transition.ChangeInfo> targets = Transition.calculateTargets(
transition.mParticipants, transition.mChanges);
- assertTrue(targets.contains(embeddedTf));
- assertTrue(targets.contains(activity));
+ assertTrue(Transition.containsChangeFor(embeddedTf, targets));
+ assertTrue(Transition.containsChangeFor(activity, targets));
}
@Test
@@ -1470,10 +1494,10 @@
participants.add(embeddedTf);
participants.add(nonEmbeddedActivity);
- final ArrayList<WindowContainer> targets = Transition.calculateTargets(
+ final ArrayList<Transition.ChangeInfo> targets = Transition.calculateTargets(
participants, changes);
final TransitionInfo info = Transition.calculateTransitionInfo(transition.mType,
- 0 /* flags */, targets, changes, mMockT);
+ 0 /* flags */, targets, mMockT);
// Background color should be set on both Activity and embedded TaskFragment.
final int expectedBackgroundColor = ColorUtils.setAlphaComponent(
@@ -1560,10 +1584,10 @@
c.windowConfiguration.setBounds(bounds);
task.onRequestedOverrideConfigurationChanged(c);
- ArrayList<WindowContainer> targets = Transition.calculateTargets(
+ ArrayList<Transition.ChangeInfo> targets = Transition.calculateTargets(
transition.mParticipants, transition.mChanges);
TransitionInfo info = Transition.calculateTransitionInfo(
- TRANSIT_CHANGE, 0, targets, transition.mChanges, mMockT);
+ TRANSIT_CHANGE, 0, targets, mMockT);
assertEquals(mockSnapshot,
info.getChange(task.mRemoteToken.toWindowContainerToken()).getSnapshot());
transition.abort();
@@ -1617,7 +1641,7 @@
private static void fillChangeMap(ArrayMap<WindowContainer, Transition.ChangeInfo> changes,
WindowContainer top) {
for (WindowContainer curr = top.getParent(); curr != null; curr = curr.getParent()) {
- changes.put(curr, new Transition.ChangeInfo(true /* vis */, false /* exChg */));
+ changes.put(curr, new Transition.ChangeInfo(curr, true /* vis */, false /* exChg */));
}
}
diff --git a/services/usb/java/com/android/server/usb/UsbPortManager.java b/services/usb/java/com/android/server/usb/UsbPortManager.java
index 4bb9de5..1399458 100644
--- a/services/usb/java/com/android/server/usb/UsbPortManager.java
+++ b/services/usb/java/com/android/server/usb/UsbPortManager.java
@@ -682,7 +682,10 @@
mHandler.sendMessage(message);
}
- public void addSimulatedPort(String portId, int supportedModes, IndentingPrintWriter pw) {
+ public void addSimulatedPort(String portId, int supportedModes,
+ boolean supportsComplianceWarnings,
+ IndentingPrintWriter pw) {
+
synchronized (mLock) {
if (mSimulatedPorts.containsKey(portId)) {
pw.println("Port with same name already exists. Please remove it first.");
@@ -692,7 +695,25 @@
pw.println("Adding simulated port: portId=" + portId
+ ", supportedModes=" + UsbPort.modeToString(supportedModes));
mSimulatedPorts.put(portId,
- new RawPortInfo(portId, supportedModes));
+ new RawPortInfo(
+ portId,
+ supportedModes,
+ UsbPortStatus.CONTAMINANT_PROTECTION_NONE,
+ UsbPortStatus.MODE_NONE,
+ false,
+ UsbPortStatus.POWER_ROLE_NONE,
+ false,
+ UsbPortStatus.DATA_ROLE_NONE,
+ false,
+ false,
+ UsbPortStatus.CONTAMINANT_PROTECTION_NONE,
+ false,
+ UsbPortStatus.CONTAMINANT_DETECTION_NOT_SUPPORTED,
+ UsbPortStatus.DATA_STATUS_UNKNOWN,
+ false,
+ UsbPortStatus.POWER_BRICK_STATUS_UNKNOWN,
+ supportsComplianceWarnings,
+ new int[] {}));
updatePortsLocked(pw, null);
}
}
diff --git a/services/usb/java/com/android/server/usb/UsbService.java b/services/usb/java/com/android/server/usb/UsbService.java
index d09f729..6eb04d9 100644
--- a/services/usb/java/com/android/server/usb/UsbService.java
+++ b/services/usb/java/com/android/server/usb/UsbService.java
@@ -987,9 +987,12 @@
mPortManager.dump(new DualDumpOutputStream(new IndentingPrintWriter(pw, " ")),
"", 0);
}
- } else if ("add-port".equals(args[0]) && args.length == 3) {
+ } else if ("add-port".equals(args[0]) && args.length >= 3) {
final String portId = args[1];
final int supportedModes;
+
+ int i;
+ boolean supportsComplianceWarnings = false;
switch (args[2]) {
case "ufp":
supportedModes = MODE_UFP;
@@ -1007,8 +1010,19 @@
pw.println("Invalid mode: " + args[2]);
return;
}
+ for (i=3; i<args.length; i++) {
+ switch (args[i]) {
+ case "--compliance-warnings":
+ supportsComplianceWarnings = true;
+ continue;
+ default:
+ pw.println("Invalid Identifier: " + args[i]);
+ }
+ }
if (mPortManager != null) {
- mPortManager.addSimulatedPort(portId, supportedModes, pw);
+ mPortManager.addSimulatedPort(portId, supportedModes,
+ supportsComplianceWarnings,
+ pw);
pw.println();
mPortManager.dump(new DualDumpOutputStream(new IndentingPrintWriter(pw, " ")),
"", 0);
@@ -1121,7 +1135,9 @@
pw.println("Dump current USB state or issue command:");
pw.println(" ports");
pw.println(" set-port-roles <id> <source|sink|no-power> <host|device|no-data>");
- pw.println(" add-port <id> <ufp|dfp|dual|none>");
+ pw.println(" add-port <id> <ufp|dfp|dual|none> <optional args>");
+ pw.println(" <optional args> include:");
+ pw.println(" --compliance-warnings: enables compliance warnings on port");
pw.println(" connect-port <id> <ufp|dfp><?> <source|sink><?> <host|device><?>");
pw.println(" (add ? suffix if mode, power role, or data role can be changed)");
pw.println(" disconnect-port <id>");
@@ -1132,7 +1148,7 @@
pw.println(" dumpsys usb set-port-roles \"default\" source device");
pw.println();
pw.println("Example USB type C port simulation with full capabilities:");
- pw.println(" dumpsys usb add-port \"matrix\" dual");
+ pw.println(" dumpsys usb add-port \"matrix\" dual --compliance-warnings");
pw.println(" dumpsys usb connect-port \"matrix\" ufp? sink? device?");
pw.println(" dumpsys usb ports");
pw.println(" dumpsys usb disconnect-port \"matrix\"");
@@ -1160,15 +1176,15 @@
pw.println(" dumpsys usb set-contaminant-status \"matrix\" false");
pw.println();
pw.println("Example simulate compliance warnings:");
- pw.println(" dumpsys usb add-port \"matrix\" dual");
+ pw.println(" dumpsys usb add-port \"matrix\" dual --compliance-warnings");
pw.println(" dumpsys usb set-compliance-reasons \"matrix\" <reason-list>");
pw.println(" dumpsys usb clear-compliance-reasons \"matrix\"");
pw.println("<reason-list> is expected to be formatted as \"1, ..., 4\"");
pw.println("with reasons that need to be simulated.");
- pw.println(" 1: debug accessory");
- pw.println(" 2: bc12");
- pw.println(" 3: missing rp");
- pw.println(" 4: type c");
+ pw.println(" 1: other");
+ pw.println(" 2: debug accessory");
+ pw.println(" 3: bc12");
+ pw.println(" 4: missing rp");
pw.println();
pw.println("Example USB device descriptors:");
pw.println(" dumpsys usb dump-descriptors -dump-short");
diff --git a/telecomm/java/android/telecom/CallEndpoint.aidl b/telecomm/java/android/telecom/CallEndpoint.aidl
new file mode 100644
index 0000000..45b2249
--- /dev/null
+++ b/telecomm/java/android/telecom/CallEndpoint.aidl
@@ -0,0 +1,22 @@
+/*
+ * Copyright 2022, 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.telecom;
+
+/**
+ * {@hide}
+ */
+parcelable CallEndpoint;
\ No newline at end of file
diff --git a/telecomm/java/android/telecom/CallEndpoint.java b/telecomm/java/android/telecom/CallEndpoint.java
new file mode 100644
index 0000000..0b2211d
--- /dev/null
+++ b/telecomm/java/android/telecom/CallEndpoint.java
@@ -0,0 +1,222 @@
+/*
+ * Copyright (C) 2022 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.telecom;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.os.Parcel;
+import android.os.ParcelUuid;
+import android.os.Parcelable;
+import android.text.TextUtils;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.Objects;
+import java.util.UUID;
+
+/**
+ * Encapsulates the endpoint where call media can flow
+ */
+public final class CallEndpoint implements Parcelable {
+ /** @hide */
+ public static final int ENDPOINT_OPERATION_SUCCESS = 0;
+ /** @hide */
+ public static final int ENDPOINT_OPERATION_FAILED = 1;
+
+ /** @hide */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef({TYPE_UNKNOWN, TYPE_EARPIECE, TYPE_BLUETOOTH, TYPE_WIRED_HEADSET, TYPE_SPEAKER,
+ TYPE_STREAMING})
+ public @interface EndpointType {}
+
+ /** Indicates that the type of endpoint through which call media flows is unknown type. */
+ public static final int TYPE_UNKNOWN = -1;
+
+ /** Indicates that the type of endpoint through which call media flows is an earpiece. */
+ public static final int TYPE_EARPIECE = 1;
+
+ /** Indicates that the type of endpoint through which call media flows is a Bluetooth. */
+ public static final int TYPE_BLUETOOTH = 2;
+
+ /** Indicates that the type of endpoint through which call media flows is a wired headset. */
+ public static final int TYPE_WIRED_HEADSET = 3;
+
+ /** Indicates that the type of endpoint through which call media flows is a speakerphone. */
+ public static final int TYPE_SPEAKER = 4;
+
+ /** Indicates that the type of endpoint through which call media flows is an external. */
+ public static final int TYPE_STREAMING = 5;
+
+ private final CharSequence mName;
+ private final int mType;
+ private final ParcelUuid mIdentifier;
+
+ /**
+ * Constructor for a {@link CallEndpoint} object.
+ *
+ * @param name Human-readable name associated with the endpoint
+ * @param type The type of endpoint through which call media being routed
+ * Allowed values:
+ * {@link #TYPE_EARPIECE}
+ * {@link #TYPE_BLUETOOTH}
+ * {@link #TYPE_WIRED_HEADSET}
+ * {@link #TYPE_SPEAKER}
+ * {@link #TYPE_STREAMING}
+ * {@link #TYPE_UNKNOWN}
+ * @param id A unique identifier for this endpoint on the device
+ */
+ public CallEndpoint(@NonNull CharSequence name, @EndpointType int type,
+ @NonNull ParcelUuid id) {
+ this.mName = name;
+ this.mType = type;
+ this.mIdentifier = id;
+ }
+
+ /** @hide */
+ public CallEndpoint(@NonNull CharSequence name, @EndpointType int type) {
+ this(name, type, new ParcelUuid(UUID.randomUUID()));
+ }
+
+ /** @hide */
+ public CallEndpoint(CallEndpoint endpoint) {
+ mName = endpoint.getEndpointName();
+ mType = endpoint.getEndpointType();
+ mIdentifier = endpoint.getIdentifier();
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public boolean equals(Object obj) {
+ if (obj == null) {
+ return false;
+ }
+ if (!(obj instanceof CallEndpoint)) {
+ return false;
+ }
+ CallEndpoint endpoint = (CallEndpoint) obj;
+ return getEndpointName().toString().contentEquals(endpoint.getEndpointName())
+ && getEndpointType() == endpoint.getEndpointType()
+ && getIdentifier().equals(endpoint.getIdentifier());
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public int hashCode() {
+ return Objects.hash(mName, mType, mIdentifier);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public String toString() {
+ return TextUtils.formatSimple("[CallEndpoint Name: %s, Type: %s, Identifier: %s]",
+ mName.toString(), endpointTypeToString(mType), mIdentifier.toString());
+ }
+
+ /**
+ * @return Human-readable name associated with the endpoint
+ */
+ @NonNull
+ public CharSequence getEndpointName() {
+ return mName;
+ }
+
+ /**
+ * @return The type of endpoint through which call media being routed
+ */
+ @EndpointType
+ public int getEndpointType() {
+ return mType;
+ }
+
+ /**
+ * @return A unique identifier for this endpoint on the device
+ */
+ @NonNull
+ public ParcelUuid getIdentifier() {
+ return mIdentifier;
+ }
+
+ /**
+ * Converts the provided endpoint type into a human-readable string representation.
+ *
+ * @param endpointType to convert into a string.
+ * @return String representation of the provided endpoint type.
+ * @hide
+ */
+ @NonNull
+ public static String endpointTypeToString(int endpointType) {
+ switch (endpointType) {
+ case TYPE_EARPIECE:
+ return "EARPIECE";
+ case TYPE_BLUETOOTH:
+ return "BLUETOOTH";
+ case TYPE_WIRED_HEADSET:
+ return "WIRED_HEADSET";
+ case TYPE_SPEAKER:
+ return "SPEAKER";
+ case TYPE_STREAMING:
+ return "EXTERNAL";
+ default:
+ return "UNKNOWN (" + endpointType + ")";
+ }
+ }
+
+ /**
+ * Responsible for creating CallEndpoint objects for deserialized Parcels.
+ */
+ public static final @android.annotation.NonNull Parcelable.Creator<CallEndpoint> CREATOR =
+ new Parcelable.Creator<CallEndpoint>() {
+
+ @Override
+ public CallEndpoint createFromParcel(Parcel source) {
+ CharSequence name = source.readCharSequence();
+ int type = source.readInt();
+ ParcelUuid id = ParcelUuid.CREATOR.createFromParcel(source);
+
+ return new CallEndpoint(name, type, id);
+ }
+
+ @Override
+ public CallEndpoint[] newArray(int size) {
+ return new CallEndpoint[size];
+ }
+ };
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void writeToParcel(@NonNull Parcel destination, int flags) {
+ destination.writeCharSequence(mName);
+ destination.writeInt(mType);
+ mIdentifier.writeToParcel(destination, flags);
+ }
+}
diff --git a/telecomm/java/android/telecom/CallEndpointException.aidl b/telecomm/java/android/telecom/CallEndpointException.aidl
new file mode 100644
index 0000000..19b43c4b4
--- /dev/null
+++ b/telecomm/java/android/telecom/CallEndpointException.aidl
@@ -0,0 +1,22 @@
+/*
+ * Copyright 2022, 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.telecom;
+
+/**
+ * {@hide}
+ */
+parcelable CallEndpointException;
\ No newline at end of file
diff --git a/telecomm/java/android/telecom/CallEndpointException.java b/telecomm/java/android/telecom/CallEndpointException.java
new file mode 100644
index 0000000..e2238928
--- /dev/null
+++ b/telecomm/java/android/telecom/CallEndpointException.java
@@ -0,0 +1,130 @@
+/*
+ * Copyright (C) 2022 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.telecom;
+
+import android.annotation.IntDef;
+import android.annotation.Nullable;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.text.TextUtils;
+
+import androidx.annotation.NonNull;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * This class represents a set of exceptions that can occur when requesting a
+ * {@link CallEndpoint} change.
+ */
+public final class CallEndpointException extends RuntimeException implements Parcelable {
+ /** @hide */
+ public static final String CHANGE_ERROR = "ChangeErrorKey";
+
+ /**
+ * The operation has failed because requested CallEndpoint does not exist.
+ */
+ public static final int ERROR_ENDPOINT_DOES_NOT_EXIST = 1;
+
+ /**
+ * The operation was not completed on time.
+ */
+ public static final int ERROR_REQUEST_TIME_OUT = 2;
+
+ /**
+ * The operation was canceled by another request.
+ */
+ public static final int ERROR_ANOTHER_REQUEST = 3;
+
+ /**
+ * The operation has failed due to an unknown or unspecified error.
+ */
+ public static final int ERROR_UNSPECIFIED = 4;
+
+ private int mCode = ERROR_UNSPECIFIED;
+ private final String mMessage;
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ dest.writeString8(mMessage);
+ dest.writeInt(mCode);
+ }
+
+ /**
+ * Responsible for creating CallEndpointException objects for deserialized Parcels.
+ */
+ public static final @android.annotation.NonNull Parcelable.Creator<CallEndpointException>
+ CREATOR = new Parcelable.Creator<>() {
+ @Override
+ public CallEndpointException createFromParcel(Parcel source) {
+ return new CallEndpointException(source.readString8(), source.readInt());
+ }
+
+ @Override
+ public CallEndpointException[] newArray(int size) {
+ return new CallEndpointException[size];
+ }
+ };
+
+ /** @hide */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef({ERROR_ENDPOINT_DOES_NOT_EXIST, ERROR_REQUEST_TIME_OUT, ERROR_ANOTHER_REQUEST,
+ ERROR_UNSPECIFIED})
+ public @interface CallEndpointErrorCode {
+ }
+
+ public CallEndpointException(@Nullable String message) {
+ super(getMessage(message, ERROR_UNSPECIFIED));
+ mMessage = message;
+ }
+
+ public CallEndpointException(@Nullable String message, @CallEndpointErrorCode int code) {
+ super(getMessage(message, code));
+ mCode = code;
+ mMessage = message;
+ }
+
+ public CallEndpointException(@Nullable String message, @CallEndpointErrorCode int code,
+ @Nullable Throwable cause) {
+ super(getMessage(message, code), cause);
+ mCode = code;
+ mMessage = message;
+ }
+
+
+ public @CallEndpointErrorCode int getCode() {
+ return mCode;
+ }
+
+ private static String getMessage(String message, int code) {
+ StringBuilder builder;
+ if (!TextUtils.isEmpty(message)) {
+ builder = new StringBuilder(message);
+ builder.append(" (code: ");
+ builder.append(code);
+ builder.append(")");
+ return builder.toString();
+ } else {
+ return "code: " + code;
+ }
+ }
+}
diff --git a/telecomm/java/android/telecom/Conference.java b/telecomm/java/android/telecom/Conference.java
index f84dd7b..f803717 100644
--- a/telecomm/java/android/telecom/Conference.java
+++ b/telecomm/java/android/telecom/Conference.java
@@ -88,6 +88,7 @@
private String mTelecomCallId;
private PhoneAccountHandle mPhoneAccount;
private CallAudioState mCallAudioState;
+ private CallEndpoint mCallEndpoint;
private int mState = Connection.STATE_NEW;
private DisconnectCause mDisconnectCause;
private int mConnectionCapabilities;
@@ -223,12 +224,26 @@
* @return The audio state of the conference, describing how its audio is currently
* being routed by the system. This is {@code null} if this Conference
* does not directly know about its audio state.
+ * @deprecated Use {@link #getCurrentCallEndpoint()},
+ * {@link #onAvailableCallEndpointsChanged(List)} and
+ * {@link #onMuteStateChanged(boolean)} instead.
*/
+ @Deprecated
public final CallAudioState getCallAudioState() {
return mCallAudioState;
}
/**
+ * Obtains the current CallEndpoint.
+ *
+ * @return An object encapsulating the CallEndpoint.
+ */
+ @NonNull
+ public final CallEndpoint getCurrentCallEndpoint() {
+ return mCallEndpoint;
+ }
+
+ /**
* Returns VideoProvider of the primary call. This can be null.
*/
public VideoProvider getVideoProvider() {
@@ -314,10 +329,35 @@
* value.
*
* @param state The new call audio state.
+ * @deprecated Use {@link #onCallEndpointChanged(CallEndpoint)},
+ * {@link #onAvailableCallEndpointsChanged(List)} and
+ * {@link #onMuteStateChanged(boolean)} instead.
*/
+ @Deprecated
public void onCallAudioStateChanged(CallAudioState state) {}
/**
+ * Notifies the {@link Conference} that the audio endpoint has been changed.
+ *
+ * @param callEndpoint The new call endpoint.
+ */
+ public void onCallEndpointChanged(@NonNull CallEndpoint callEndpoint) {}
+
+ /**
+ * Notifies the {@link Conference} that the available call endpoints have been changed.
+ *
+ * @param availableEndpoints The available call endpoints.
+ */
+ public void onAvailableCallEndpointsChanged(@NonNull List<CallEndpoint> availableEndpoints) {}
+
+ /**
+ * Notifies the {@link Conference} that its audio mute state has been changed.
+ *
+ * @param isMuted The new mute state.
+ */
+ public void onMuteStateChanged(boolean isMuted) {}
+
+ /**
* Notifies the {@link Conference} that a {@link Connection} has been added to it.
*
* @param connection The newly added connection.
@@ -730,6 +770,40 @@
onCallAudioStateChanged(state);
}
+ /**
+ * Inform this Conference that the audio endpoint has been changed.
+ *
+ * @param endpoint The new call endpoint.
+ * @hide
+ */
+ final void setCallEndpoint(CallEndpoint endpoint) {
+ Log.d(this, "setCallEndpoint %s", endpoint);
+ mCallEndpoint = endpoint;
+ onCallEndpointChanged(endpoint);
+ }
+
+ /**
+ * Inform this Conference that the available call endpoints have been changed.
+ *
+ * @param availableEndpoints The available call endpoints.
+ * @hide
+ */
+ final void setAvailableCallEndpoints(List<CallEndpoint> availableEndpoints) {
+ Log.d(this, "setAvailableCallEndpoints");
+ onAvailableCallEndpointsChanged(availableEndpoints);
+ }
+
+ /**
+ * Inform this Conference that its audio mute state has been changed.
+ *
+ * @param isMuted The new mute state.
+ * @hide
+ */
+ final void setMuteState(boolean isMuted) {
+ Log.d(this, "setMuteState %s", isMuted);
+ onMuteStateChanged(isMuted);
+ }
+
private void setState(int newState) {
if (mState != newState) {
int oldState = mState;
diff --git a/telecomm/java/android/telecom/Connection.java b/telecomm/java/android/telecom/Connection.java
index 568c8ab..4656226 100644
--- a/telecomm/java/android/telecom/Connection.java
+++ b/telecomm/java/android/telecom/Connection.java
@@ -19,6 +19,7 @@
import static android.Manifest.permission.MODIFY_PHONE_STATE;
import android.Manifest;
+import android.annotation.CallbackExecutor;
import android.annotation.ElapsedRealtimeLong;
import android.annotation.IntDef;
import android.annotation.IntRange;
@@ -39,6 +40,7 @@
import android.os.IBinder;
import android.os.Looper;
import android.os.Message;
+import android.os.OutcomeReceiver;
import android.os.Parcel;
import android.os.ParcelFileDescriptor;
import android.os.Parcelable;
@@ -69,6 +71,7 @@
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.Executor;
/**
* Represents a phone call or connection to a remote endpoint that carries voice and/or video
@@ -1280,6 +1283,8 @@
/** @hide */
public void onPhoneAccountChanged(Connection c, PhoneAccountHandle pHandle) {}
public void onConnectionTimeReset(Connection c) {}
+ public void onEndpointChanged(Connection c, CallEndpoint endpoint, Executor executor,
+ OutcomeReceiver<Void, CallEndpointException> callback) {}
}
/**
@@ -2162,6 +2167,7 @@
private PhoneAccountHandle mPhoneAccountHandle;
private int mState = STATE_NEW;
private CallAudioState mCallAudioState;
+ private CallEndpoint mCallEndpoint;
private Uri mAddress;
private int mAddressPresentation;
private String mCallerDisplayName;
@@ -2290,7 +2296,11 @@
* @return The audio state of the connection, describing how its audio is currently
* being routed by the system. This is {@code null} if this Connection
* does not directly know about its audio state.
+ * @deprecated Use {@link #getCurrentCallEndpoint()},
+ * {@link #onAvailableCallEndpointsChanged(List)} and
+ * {@link #onMuteStateChanged(boolean)} instead.
*/
+ @Deprecated
public final CallAudioState getCallAudioState() {
return mCallAudioState;
}
@@ -2457,6 +2467,43 @@
}
/**
+ * Inform this Connection that the audio endpoint has been changed.
+ *
+ * @param endpoint The new call endpoint.
+ * @hide
+ */
+ final void setCallEndpoint(CallEndpoint endpoint) {
+ checkImmutable();
+ Log.d(this, "setCallEndpoint %s", endpoint);
+ mCallEndpoint = endpoint;
+ onCallEndpointChanged(endpoint);
+ }
+
+ /**
+ * Inform this Connection that the available call endpoints have been changed.
+ *
+ * @param availableEndpoints The available call endpoints.
+ * @hide
+ */
+ final void setAvailableCallEndpoints(List<CallEndpoint> availableEndpoints) {
+ checkImmutable();
+ Log.d(this, "setAvailableCallEndpoints");
+ onAvailableCallEndpointsChanged(availableEndpoints);
+ }
+
+ /**
+ * Inform this Connection that its audio mute state has been changed.
+ *
+ * @param isMuted The new mute state.
+ * @hide
+ */
+ final void setMuteState(boolean isMuted) {
+ checkImmutable();
+ Log.d(this, "setMuteState %s", isMuted);
+ onMuteStateChanged(isMuted);
+ }
+
+ /**
* @param state An integer value of a {@code STATE_*} constant.
* @return A string representation of the value.
*/
@@ -3081,7 +3128,10 @@
* @param route The audio route to use (one of {@link CallAudioState#ROUTE_BLUETOOTH},
* {@link CallAudioState#ROUTE_EARPIECE}, {@link CallAudioState#ROUTE_SPEAKER}, or
* {@link CallAudioState#ROUTE_WIRED_HEADSET}).
+ * @deprecated Use {@link #requestCallEndpointChange(CallEndpoint, Executor, OutcomeReceiver)}
+ * instead.
*/
+ @Deprecated
public final void setAudioRoute(int route) {
for (Listener l : mListeners) {
l.onAudioRouteChanged(this, route, null);
@@ -3101,7 +3151,10 @@
* <p>
* See also {@link InCallService#requestBluetoothAudio(BluetoothDevice)}
* @param bluetoothDevice The bluetooth device to connect to.
+ * @deprecated Use {@link #requestCallEndpointChange(CallEndpoint, Executor, OutcomeReceiver)}
+ * instead.
*/
+ @Deprecated
public void requestBluetoothAudio(@NonNull BluetoothDevice bluetoothDevice) {
for (Listener l : mListeners) {
l.onAudioRouteChanged(this, CallAudioState.ROUTE_BLUETOOTH,
@@ -3110,6 +3163,40 @@
}
/**
+ * Request audio routing to a specific CallEndpoint. Clients should not define their own
+ * CallEndpoint when requesting a change. Instead, the new endpoint should be one of the valid
+ * endpoints provided by {@link #onAvailableCallEndpointsChanged(List)}.
+ * When this request is honored, there will be change to the {@link #getCurrentCallEndpoint()}.
+ * <p>
+ * Used by self-managed {@link ConnectionService}s which wish to change the CallEndpoint for a
+ * self-managed {@link Connection} (see {@link PhoneAccount#CAPABILITY_SELF_MANAGED}.)
+ * <p>
+ * See also
+ * {@link InCallService#requestCallEndpointChange(CallEndpoint, Executor, OutcomeReceiver)}.
+ *
+ * @param endpoint The call endpoint to use.
+ * @param executor The executor of where the callback will execute.
+ * @param callback The callback to notify the result of the endpoint change.
+ */
+ public final void requestCallEndpointChange(@NonNull CallEndpoint endpoint,
+ @NonNull @CallbackExecutor Executor executor,
+ @NonNull OutcomeReceiver<Void, CallEndpointException> callback) {
+ for (Listener l : mListeners) {
+ l.onEndpointChanged(this, endpoint, executor, callback);
+ }
+ }
+
+ /**
+ * Obtains the current CallEndpoint.
+ *
+ * @return An object encapsulating the CallEndpoint.
+ */
+ @NonNull
+ public final CallEndpoint getCurrentCallEndpoint() {
+ return mCallEndpoint;
+ }
+
+ /**
* Informs listeners that a previously requested RTT session via
* {@link ConnectionRequest#isRequestingRtt()} or
* {@link #onStartRtt(RttTextStream)} has succeeded.
@@ -3160,10 +3247,35 @@
* Notifies this Connection that the {@link #getCallAudioState()} property has a new value.
*
* @param state The new connection audio state.
+ * @deprecated Use {@link #onCallEndpointChanged(CallEndpoint)},
+ * {@link #onAvailableCallEndpointsChanged(List)} and
+ * {@link #onMuteStateChanged(boolean)} instead.
*/
+ @Deprecated
public void onCallAudioStateChanged(CallAudioState state) {}
/**
+ * Notifies this Connection that the audio endpoint has been changed.
+ *
+ * @param callEndpoint The current CallEndpoint.
+ */
+ public void onCallEndpointChanged(@NonNull CallEndpoint callEndpoint) {}
+
+ /**
+ * Notifies this Connection that the available call endpoints have been changed.
+ *
+ * @param availableEndpoints The set of available CallEndpoint.
+ */
+ public void onAvailableCallEndpointsChanged(@NonNull List<CallEndpoint> availableEndpoints) {}
+
+ /**
+ * Notifies this Connection that its audio mute state has been changed.
+ *
+ * @param isMuted The current mute state.
+ */
+ public void onMuteStateChanged(boolean isMuted) {}
+
+ /**
* Inform this Connection when it will or will not be tracked by an {@link InCallService} which
* can provide an InCall UI.
* This is primarily intended for use by Connections reported by self-managed
diff --git a/telecomm/java/android/telecom/ConnectionService.java b/telecomm/java/android/telecom/ConnectionService.java
index f341bc2..4d6caf8 100755
--- a/telecomm/java/android/telecom/ConnectionService.java
+++ b/telecomm/java/android/telecom/ConnectionService.java
@@ -31,6 +31,7 @@
import android.os.IBinder;
import android.os.Looper;
import android.os.Message;
+import android.os.OutcomeReceiver;
import android.os.ParcelFileDescriptor;
import android.os.RemoteException;
import android.telecom.Logging.Session;
@@ -48,6 +49,7 @@
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.Executor;
/**
* An abstract service that should be implemented by any apps which either:
@@ -164,6 +166,9 @@
private static final String SESSION_CREATE_CONF = "CS.crConf";
private static final String SESSION_CREATE_CONF_COMPLETE = "CS.crConfC";
private static final String SESSION_CREATE_CONF_FAILED = "CS.crConfF";
+ private static final String SESSION_CALL_ENDPOINT_CHANGED = "CS.oCEC";
+ private static final String SESSION_AVAILABLE_CALL_ENDPOINTS_CHANGED = "CS.oACEC";
+ private static final String SESSION_MUTE_STATE_CHANGED = "CS.oMSC";
private static final int MSG_ADD_CONNECTION_SERVICE_ADAPTER = 1;
private static final int MSG_CREATE_CONNECTION = 2;
@@ -208,6 +213,9 @@
private static final int MSG_ON_CALL_FILTERING_COMPLETED = 42;
private static final int MSG_ON_USING_ALTERNATIVE_UI = 43;
private static final int MSG_ON_TRACKED_BY_NON_UI_SERVICE = 44;
+ private static final int MSG_ON_CALL_ENDPOINT_CHANGED = 45;
+ private static final int MSG_ON_AVAILABLE_CALL_ENDPOINTS_CHANGED = 46;
+ private static final int MSG_ON_MUTE_STATE_CHANGED = 47;
private static Connection sNullConnection;
@@ -592,6 +600,51 @@
}
@Override
+ public void onCallEndpointChanged(String callId, CallEndpoint callEndpoint,
+ Session.Info sessionInfo) {
+ Log.startSession(sessionInfo, SESSION_CALL_ENDPOINT_CHANGED);
+ try {
+ SomeArgs args = SomeArgs.obtain();
+ args.arg1 = callId;
+ args.arg2 = callEndpoint;
+ args.arg3 = Log.createSubsession();
+ mHandler.obtainMessage(MSG_ON_CALL_ENDPOINT_CHANGED, args).sendToTarget();
+ } finally {
+ Log.endSession();
+ }
+ }
+
+ @Override
+ public void onAvailableCallEndpointsChanged(String callId,
+ List<CallEndpoint> availableCallEndpoints, Session.Info sessionInfo) {
+ Log.startSession(sessionInfo, SESSION_AVAILABLE_CALL_ENDPOINTS_CHANGED);
+ try {
+ SomeArgs args = SomeArgs.obtain();
+ args.arg1 = callId;
+ args.arg2 = availableCallEndpoints;
+ args.arg3 = Log.createSubsession();
+ mHandler.obtainMessage(MSG_ON_AVAILABLE_CALL_ENDPOINTS_CHANGED, args)
+ .sendToTarget();
+ } finally {
+ Log.endSession();
+ }
+ }
+
+ @Override
+ public void onMuteStateChanged(String callId, boolean isMuted, Session.Info sessionInfo) {
+ Log.startSession(sessionInfo, SESSION_MUTE_STATE_CHANGED);
+ try {
+ SomeArgs args = SomeArgs.obtain();
+ args.arg1 = callId;
+ args.arg2 = isMuted;
+ args.arg3 = Log.createSubsession();
+ mHandler.obtainMessage(MSG_ON_MUTE_STATE_CHANGED, args).sendToTarget();
+ } finally {
+ Log.endSession();
+ }
+ }
+
+ @Override
public void onUsingAlternativeUi(String callId, boolean usingAlternativeUiShowing,
Session.Info sessionInfo) {
Log.startSession(sessionInfo, SESSION_USING_ALTERNATIVE_UI);
@@ -1527,6 +1580,48 @@
case MSG_CONNECTION_SERVICE_FOCUS_LOST:
onConnectionServiceFocusLost();
break;
+ case MSG_ON_CALL_ENDPOINT_CHANGED: {
+ SomeArgs args = (SomeArgs) msg.obj;
+ Log.continueSession((Session) args.arg3,
+ SESSION_HANDLER + SESSION_CALL_AUDIO_SC);
+ try {
+ String callId = (String) args.arg1;
+ CallEndpoint callEndpoint = (CallEndpoint) args.arg2;
+ onCallEndpointChanged(callId, callEndpoint);
+ } finally {
+ args.recycle();
+ Log.endSession();
+ }
+ break;
+ }
+ case MSG_ON_AVAILABLE_CALL_ENDPOINTS_CHANGED: {
+ SomeArgs args = (SomeArgs) msg.obj;
+ Log.continueSession((Session) args.arg3,
+ SESSION_HANDLER + SESSION_CALL_AUDIO_SC);
+ try {
+ String callId = (String) args.arg1;
+ List<CallEndpoint> availableCallEndpoints = (List<CallEndpoint>) args.arg2;
+ onAvailableCallEndpointsChanged(callId, availableCallEndpoints);
+ } finally {
+ args.recycle();
+ Log.endSession();
+ }
+ break;
+ }
+ case MSG_ON_MUTE_STATE_CHANGED: {
+ SomeArgs args = (SomeArgs) msg.obj;
+ Log.continueSession((Session) args.arg3,
+ SESSION_HANDLER + SESSION_CALL_AUDIO_SC);
+ try {
+ String callId = (String) args.arg1;
+ boolean isMuted = (boolean) args.arg2;
+ onMuteStateChanged(callId, isMuted);
+ } finally {
+ args.recycle();
+ Log.endSession();
+ }
+ break;
+ }
default:
break;
}
@@ -1916,6 +2011,15 @@
mAdapter.resetConnectionTime(id);
}
}
+
+ @Override
+ public void onEndpointChanged(Connection c, CallEndpoint endpoint, Executor executor,
+ OutcomeReceiver<Void, CallEndpointException> callback) {
+ String id = mIdByConnection.get(c);
+ if (id != null) {
+ mAdapter.requestCallEndpointChange(id, endpoint, executor, callback);
+ }
+ }
};
/** {@inheritDoc} */
@@ -2313,6 +2417,36 @@
}
}
+ private void onCallEndpointChanged(String callId, CallEndpoint callEndpoint) {
+ Log.i(this, "onCallEndpointChanged %s %s", callId, callEndpoint);
+ if (mConnectionById.containsKey(callId)) {
+ findConnectionForAction(callId, "onCallEndpointChanged").setCallEndpoint(callEndpoint);
+ } else {
+ findConferenceForAction(callId, "onCallEndpointChanged").setCallEndpoint(callEndpoint);
+ }
+ }
+
+ private void onAvailableCallEndpointsChanged(String callId,
+ List<CallEndpoint> availableCallEndpoints) {
+ Log.i(this, "onAvailableCallEndpointsChanged %s", callId);
+ if (mConnectionById.containsKey(callId)) {
+ findConnectionForAction(callId, "onAvailableCallEndpointsChanged")
+ .setAvailableCallEndpoints(availableCallEndpoints);
+ } else {
+ findConferenceForAction(callId, "onAvailableCallEndpointsChanged")
+ .setAvailableCallEndpoints(availableCallEndpoints);
+ }
+ }
+
+ private void onMuteStateChanged(String callId, boolean isMuted) {
+ Log.i(this, "onMuteStateChanged %s %s", callId, isMuted);
+ if (mConnectionById.containsKey(callId)) {
+ findConnectionForAction(callId, "onMuteStateChanged").setMuteState(isMuted);
+ } else {
+ findConferenceForAction(callId, "onMuteStateChanged").setMuteState(isMuted);
+ }
+ }
+
private void onUsingAlternativeUi(String callId, boolean isUsingAlternativeUi) {
Log.i(this, "onUsingAlternativeUi %s %s", callId, isUsingAlternativeUi);
if (mConnectionById.containsKey(callId)) {
diff --git a/telecomm/java/android/telecom/ConnectionServiceAdapter.java b/telecomm/java/android/telecom/ConnectionServiceAdapter.java
index f8a6cf0..39928ef 100644
--- a/telecomm/java/android/telecom/ConnectionServiceAdapter.java
+++ b/telecomm/java/android/telecom/ConnectionServiceAdapter.java
@@ -17,9 +17,12 @@
package android.telecom;
import android.net.Uri;
+import android.os.Binder;
import android.os.Bundle;
import android.os.IBinder.DeathRecipient;
+import android.os.OutcomeReceiver;
import android.os.RemoteException;
+import android.os.ResultReceiver;
import com.android.internal.telecom.IConnectionServiceAdapter;
import com.android.internal.telecom.RemoteServiceCallback;
@@ -29,6 +32,7 @@
import java.util.List;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.Executor;
/**
* Provides methods for IConnectionService implementations to interact with the system phone app.
@@ -567,6 +571,41 @@
}
}
+ /**
+ * Sets the call endpoint associated with a {@link Connection}.
+ *
+ * @param callId The unique ID of the call.
+ * @param endpoint The new call endpoint (see {@link CallEndpoint}).
+ * @param executor The executor of where the callback will execute.
+ * @param callback The callback to notify the result of the endpoint change.
+ */
+ void requestCallEndpointChange(String callId, CallEndpoint endpoint, Executor executor,
+ OutcomeReceiver<Void, CallEndpointException> callback) {
+ Log.v(this, "requestCallEndpointChange");
+ for (IConnectionServiceAdapter adapter : mAdapters) {
+ try {
+ adapter.requestCallEndpointChange(callId, endpoint, new ResultReceiver(null) {
+ @Override
+ protected void onReceiveResult(int resultCode, Bundle result) {
+ super.onReceiveResult(resultCode, result);
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ if (resultCode == CallEndpoint.ENDPOINT_OPERATION_SUCCESS) {
+ executor.execute(() -> callback.onResult(null));
+ } else {
+ executor.execute(() -> callback.onError(result.getParcelable(
+ CallEndpointException.CHANGE_ERROR,
+ CallEndpointException.class)));
+ }
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }}, Log.getExternalSession());
+ } catch (RemoteException ignored) {
+ Log.d(this, "Remote exception calling requestCallEndpointChange");
+ }
+ }
+ }
/**
* Informs Telecom of a connection level event.
diff --git a/telecomm/java/android/telecom/ConnectionServiceAdapterServant.java b/telecomm/java/android/telecom/ConnectionServiceAdapterServant.java
index 6c1ea32..c95e14f 100644
--- a/telecomm/java/android/telecom/ConnectionServiceAdapterServant.java
+++ b/telecomm/java/android/telecom/ConnectionServiceAdapterServant.java
@@ -21,6 +21,7 @@
import android.os.Handler;
import android.os.Message;
import android.os.RemoteException;
+import android.os.ResultReceiver;
import android.telecom.Logging.Session;
import com.android.internal.os.SomeArgs;
@@ -692,6 +693,12 @@
args.arg2 = sessionInfo;
mHandler.obtainMessage(MSG_SET_CALL_DIRECTION, args).sendToTarget();
}
+
+ @Override
+ public void requestCallEndpointChange(String callId, CallEndpoint endpoint,
+ ResultReceiver callback, Session.Info sessionInfo) {
+ // Do nothing
+ }
};
public ConnectionServiceAdapterServant(IConnectionServiceAdapter delegate) {
diff --git a/telecomm/java/android/telecom/InCallAdapter.java b/telecomm/java/android/telecom/InCallAdapter.java
index ab35aff..7770145 100755
--- a/telecomm/java/android/telecom/InCallAdapter.java
+++ b/telecomm/java/android/telecom/InCallAdapter.java
@@ -19,12 +19,16 @@
import android.annotation.NonNull;
import android.bluetooth.BluetoothDevice;
import android.net.Uri;
+import android.os.Binder;
import android.os.Bundle;
+import android.os.OutcomeReceiver;
import android.os.RemoteException;
+import android.os.ResultReceiver;
import com.android.internal.telecom.IInCallAdapter;
import java.util.List;
+import java.util.concurrent.Executor;
/**
* Receives commands from {@link InCallService} implementations which should be executed by
@@ -227,6 +231,39 @@
}
/**
+ * Request audio routing to a specific CallEndpoint.. See {@link CallEndpoint}.
+ *
+ * @param endpoint The call endpoint to use.
+ * @param executor The executor of where the callback will execute.
+ * @param callback The callback to notify the result of the endpoint change.
+ */
+ public void requestCallEndpointChange(CallEndpoint endpoint, Executor executor,
+ OutcomeReceiver<Void, CallEndpointException> callback) {
+ try {
+ mAdapter.requestCallEndpointChange(endpoint, new ResultReceiver(null) {
+ @Override
+ protected void onReceiveResult(int resultCode, Bundle result) {
+ super.onReceiveResult(resultCode, result);
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ if (resultCode == CallEndpoint.ENDPOINT_OPERATION_SUCCESS) {
+ executor.execute(() -> callback.onResult(null));
+ } else {
+ executor.execute(() -> callback.onError(
+ result.getParcelable(CallEndpointException.CHANGE_ERROR,
+ CallEndpointException.class)));
+ }
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+ });
+ } catch (RemoteException e) {
+ Log.d(this, "Remote exception calling requestCallEndpointChange");
+ }
+ }
+
+ /**
* Instructs Telecom to play a dual-tone multi-frequency signaling (DTMF) tone in a call.
*
* Any other currently playing DTMF tone in the specified call is immediately stopped.
diff --git a/telecomm/java/android/telecom/InCallService.java b/telecomm/java/android/telecom/InCallService.java
index 64a86db..13a0458 100644
--- a/telecomm/java/android/telecom/InCallService.java
+++ b/telecomm/java/android/telecom/InCallService.java
@@ -16,6 +16,7 @@
package android.telecom;
+import android.annotation.CallbackExecutor;
import android.annotation.NonNull;
import android.annotation.SdkConstant;
import android.annotation.SystemApi;
@@ -31,6 +32,7 @@
import android.os.IBinder;
import android.os.Looper;
import android.os.Message;
+import android.os.OutcomeReceiver;
import android.view.Surface;
import com.android.internal.os.SomeArgs;
@@ -39,6 +41,8 @@
import java.util.Collections;
import java.util.List;
+import java.util.Objects;
+import java.util.concurrent.Executor;
/**
* This service is implemented by an app that wishes to provide functionality for managing
@@ -269,6 +273,11 @@
private static final int MSG_ON_RTT_INITIATION_FAILURE = 11;
private static final int MSG_ON_HANDOVER_FAILED = 12;
private static final int MSG_ON_HANDOVER_COMPLETE = 13;
+ private static final int MSG_ON_CALL_ENDPOINT_CHANGED = 14;
+ private static final int MSG_ON_AVAILABLE_CALL_ENDPOINTS_CHANGED = 15;
+ private static final int MSG_ON_MUTE_STATE_CHANGED = 16;
+
+ private CallEndpoint mCallEndpoint;
/** Default Handler used to consolidate binder method calls onto a single thread. */
private final Handler mHandler = new Handler(Looper.getMainLooper()) {
@@ -350,6 +359,23 @@
mPhone.internalOnHandoverComplete(callId);
break;
}
+ case MSG_ON_CALL_ENDPOINT_CHANGED: {
+ CallEndpoint endpoint = (CallEndpoint) msg.obj;
+ if (!Objects.equals(mCallEndpoint, endpoint)) {
+ mCallEndpoint = endpoint;
+ InCallService.this.onCallEndpointChanged(mCallEndpoint);
+ }
+ break;
+ }
+ case MSG_ON_AVAILABLE_CALL_ENDPOINTS_CHANGED: {
+ InCallService.this.onAvailableCallEndpointsChanged(
+ (List<CallEndpoint>) msg.obj);
+ break;
+ }
+ case MSG_ON_MUTE_STATE_CHANGED: {
+ InCallService.this.onMuteStateChanged((boolean) msg.obj);
+ break;
+ }
default:
break;
}
@@ -392,6 +418,22 @@
}
@Override
+ public void onCallEndpointChanged(CallEndpoint callEndpoint) {
+ mHandler.obtainMessage(MSG_ON_CALL_ENDPOINT_CHANGED, callEndpoint).sendToTarget();
+ }
+
+ @Override
+ public void onAvailableCallEndpointsChanged(List<CallEndpoint> availableEndpoints) {
+ mHandler.obtainMessage(MSG_ON_AVAILABLE_CALL_ENDPOINTS_CHANGED, availableEndpoints)
+ .sendToTarget();
+ }
+
+ @Override
+ public void onMuteStateChanged(boolean isMuted) {
+ mHandler.obtainMessage(MSG_ON_MUTE_STATE_CHANGED, isMuted).sendToTarget();
+ }
+
+ @Override
public void bringToForeground(boolean showDialpad) {
mHandler.obtainMessage(MSG_BRING_TO_FOREGROUND, showDialpad ? 1 : 0, 0).sendToTarget();
}
@@ -559,7 +601,11 @@
*
* @return An object encapsulating the audio state. Returns null if the service is not
* fully initialized.
+ * @deprecated Use {@link #getCurrentCallEndpoint()},
+ * {@link #onAvailableCallEndpointsChanged(List)} and
+ * {@link #onMuteStateChanged(boolean)} instead.
*/
+ @Deprecated
public final CallAudioState getCallAudioState() {
return mPhone == null ? null : mPhone.getCallAudioState();
}
@@ -581,7 +627,10 @@
* be change to the {@link #getCallAudioState()}.
*
* @param route The audio route to use.
+ * @deprecated Use {@link #requestCallEndpointChange(CallEndpoint, Executor, OutcomeReceiver)}
+ * instead.
*/
+ @Deprecated
public final void setAudioRoute(int route) {
if (mPhone != null) {
mPhone.setAudioRoute(route);
@@ -596,7 +645,10 @@
* {@link CallAudioState#getSupportedBluetoothDevices()}
*
* @param bluetoothDevice The bluetooth device to connect to.
+ * @deprecated Use {@link #requestCallEndpointChange(CallEndpoint, Executor, OutcomeReceiver)}
+ * instead.
*/
+ @Deprecated
public final void requestBluetoothAudio(@NonNull BluetoothDevice bluetoothDevice) {
if (mPhone != null) {
mPhone.requestBluetoothAudio(bluetoothDevice.getAddress());
@@ -604,6 +656,34 @@
}
/**
+ * Request audio routing to a specific CallEndpoint. Clients should not define their own
+ * CallEndpoint when requesting a change. Instead, the new endpoint should be one of the valid
+ * endpoints provided by {@link #onAvailableCallEndpointsChanged(List)}.
+ * When this request is honored, there will be change to the {@link #getCurrentCallEndpoint()}.
+ *
+ * @param endpoint The call endpoint to use.
+ * @param executor The executor of where the callback will execute.
+ * @param callback The callback to notify the result of the endpoint change.
+ */
+ public final void requestCallEndpointChange(@NonNull CallEndpoint endpoint,
+ @NonNull @CallbackExecutor Executor executor,
+ @NonNull OutcomeReceiver<Void, CallEndpointException> callback) {
+ if (mPhone != null) {
+ mPhone.requestCallEndpointChange(endpoint, executor, callback);
+ }
+ }
+
+ /**
+ * Obtains the current CallEndpoint.
+ *
+ * @return An object encapsulating the CallEndpoint.
+ */
+ @NonNull
+ public final CallEndpoint getCurrentCallEndpoint() {
+ return mCallEndpoint;
+ }
+
+ /**
* Invoked when the {@code Phone} has been created. This is a signal to the in-call experience
* to start displaying in-call information to the user. Each instance of {@code InCallService}
* will have only one {@code Phone}, and this method will be called exactly once in the lifetime
@@ -648,11 +728,39 @@
* Called when the audio state changes.
*
* @param audioState The new {@link CallAudioState}.
+ * @deprecated Use {@link #onCallEndpointChanged(CallEndpoint)},
+ * {@link #onAvailableCallEndpointsChanged(List)} and
+ * {@link #onMuteStateChanged(boolean)} instead.
*/
+ @Deprecated
public void onCallAudioStateChanged(CallAudioState audioState) {
}
/**
+ * Called when the current CallEndpoint changes.
+ *
+ * @param callEndpoint The current CallEndpoint {@link CallEndpoint}.
+ */
+ public void onCallEndpointChanged(@NonNull CallEndpoint callEndpoint) {
+ }
+
+ /**
+ * Called when the available CallEndpoint changes.
+ *
+ * @param availableEndpoints The set of available CallEndpoint {@link CallEndpoint}.
+ */
+ public void onAvailableCallEndpointsChanged(@NonNull List<CallEndpoint> availableEndpoints) {
+ }
+
+ /**
+ * Called when the mute state changes.
+ *
+ * @param isMuted The current mute state.
+ */
+ public void onMuteStateChanged(boolean isMuted) {
+ }
+
+ /**
* Called to bring the in-call screen to the foreground. The in-call experience should
* respond immediately by coming to the foreground to inform the user of the state of
* ongoing {@code Call}s.
diff --git a/telecomm/java/android/telecom/Phone.java b/telecomm/java/android/telecom/Phone.java
index bc0a146..95a8e16 100644
--- a/telecomm/java/android/telecom/Phone.java
+++ b/telecomm/java/android/telecom/Phone.java
@@ -16,11 +16,14 @@
package android.telecom;
+import android.annotation.CallbackExecutor;
+import android.annotation.NonNull;
import android.annotation.SystemApi;
import android.bluetooth.BluetoothDevice;
import android.compat.annotation.UnsupportedAppUsage;
import android.os.Build;
import android.os.Bundle;
+import android.os.OutcomeReceiver;
import android.util.ArrayMap;
import com.android.internal.annotations.GuardedBy;
@@ -30,6 +33,7 @@
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.CopyOnWriteArrayList;
+import java.util.concurrent.Executor;
/**
* A unified virtual device providing a means of voice (and other) communication on a device.
@@ -378,6 +382,21 @@
}
/**
+ * Request audio routing to a specific CallEndpoint. When this request is honored, there will
+ * be change to the {@link #getCurrentCallEndpoint()}.
+ *
+ * @param endpoint The call endpoint to use.
+ * @param executor The executor of where the callback will execute.
+ * @param callback The callback to notify the result of the endpoint change.
+ * @hide
+ */
+ public void requestCallEndpointChange(@NonNull CallEndpoint endpoint,
+ @NonNull @CallbackExecutor Executor executor,
+ @NonNull OutcomeReceiver<Void, CallEndpointException> callback) {
+ mInCallAdapter.requestCallEndpointChange(endpoint, executor, callback);
+ }
+
+ /**
* Turns the proximity sensor on. When this request is made, the proximity sensor will
* become active, and the touch screen and display will be turned off when the user's face
* is detected to be in close proximity to the screen. This operation is a no-op on devices
diff --git a/telecomm/java/android/telecom/RemoteConnection.java b/telecomm/java/android/telecom/RemoteConnection.java
index 7a6fddb..8b2b51e 100644
--- a/telecomm/java/android/telecom/RemoteConnection.java
+++ b/telecomm/java/android/telecom/RemoteConnection.java
@@ -36,12 +36,10 @@
import com.android.internal.telecom.IVideoProvider;
import java.util.ArrayList;
-import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
-import java.util.stream.Collectors;
/**
* A connection provided to a {@link ConnectionService} by another {@code ConnectionService}
diff --git a/telecomm/java/android/telecom/RemoteConnectionService.java b/telecomm/java/android/telecom/RemoteConnectionService.java
index efe35d2..6561732 100644
--- a/telecomm/java/android/telecom/RemoteConnectionService.java
+++ b/telecomm/java/android/telecom/RemoteConnectionService.java
@@ -21,6 +21,7 @@
import android.os.IBinder;
import android.os.IBinder.DeathRecipient;
import android.os.RemoteException;
+import android.os.ResultReceiver;
import android.telecom.Logging.Session;
import com.android.internal.telecom.IConnectionService;
@@ -510,6 +511,12 @@
public void setCallDirection(String callId, int direction, Session.Info sessionInfo) {
// Do nothing
}
+
+ @Override
+ public void requestCallEndpointChange(String callId, CallEndpoint endpoint,
+ ResultReceiver callback, Session.Info sessionInfo) {
+ // Do nothing
+ }
};
private final ConnectionServiceAdapterServant mServant =
diff --git a/telecomm/java/com/android/internal/telecom/IConnectionService.aidl b/telecomm/java/com/android/internal/telecom/IConnectionService.aidl
index d72f8aa..29617f2 100644
--- a/telecomm/java/com/android/internal/telecom/IConnectionService.aidl
+++ b/telecomm/java/com/android/internal/telecom/IConnectionService.aidl
@@ -20,6 +20,7 @@
import android.os.Bundle;
import android.os.ParcelFileDescriptor;
import android.telecom.CallAudioState;
+import android.telecom.CallEndpoint;
import android.telecom.Connection;
import android.telecom.ConnectionRequest;
import android.telecom.Logging.Session;
@@ -98,6 +99,14 @@
void onCallAudioStateChanged(String activeCallId, in CallAudioState callAudioState,
in Session.Info sessionInfo);
+ void onCallEndpointChanged(String activeCallId, in CallEndpoint callEndpoint,
+ in Session.Info sessionInfo);
+
+ void onAvailableCallEndpointsChanged(String activeCallId,
+ in List<CallEndpoint> availableCallEndpoints, in Session.Info sessionInfo);
+
+ void onMuteStateChanged(String activeCallId, boolean isMuted, in Session.Info sessionInfo);
+
void playDtmfTone(String callId, char digit, in Session.Info sessionInfo);
void stopDtmfTone(String callId, in Session.Info sessionInfo);
diff --git a/telecomm/java/com/android/internal/telecom/IConnectionServiceAdapter.aidl b/telecomm/java/com/android/internal/telecom/IConnectionServiceAdapter.aidl
index 3fd7f949..6838fbd 100644
--- a/telecomm/java/com/android/internal/telecom/IConnectionServiceAdapter.aidl
+++ b/telecomm/java/com/android/internal/telecom/IConnectionServiceAdapter.aidl
@@ -19,6 +19,8 @@
import android.app.PendingIntent;
import android.net.Uri;
import android.os.Bundle;
+import android.os.ResultReceiver;
+import android.telecom.CallEndpoint;
import android.telecom.ConnectionRequest;
import android.telecom.DisconnectCause;
import android.telecom.Logging.Session;
@@ -113,6 +115,9 @@
void setAudioRoute(String callId, int audioRoute, String bluetoothAddress,
in Session.Info sessionInfo);
+ void requestCallEndpointChange(String callId, in CallEndpoint endpoint,
+ in ResultReceiver callback, in Session.Info sessionInfo);
+
void onConnectionEvent(String callId, String event, in Bundle extras,
in Session.Info sessionInfo);
diff --git a/telecomm/java/com/android/internal/telecom/IInCallAdapter.aidl b/telecomm/java/com/android/internal/telecom/IInCallAdapter.aidl
index edf1cf4..e381ce8 100755
--- a/telecomm/java/com/android/internal/telecom/IInCallAdapter.aidl
+++ b/telecomm/java/com/android/internal/telecom/IInCallAdapter.aidl
@@ -18,7 +18,9 @@
import android.net.Uri;
import android.os.Bundle;
+import android.os.ResultReceiver;
import android.telecom.PhoneAccountHandle;
+import android.telecom.CallEndpoint;
/**
* Internal remote callback interface for in-call services.
@@ -50,6 +52,8 @@
void setAudioRoute(int route, String bluetoothAddress);
+ void requestCallEndpointChange(in CallEndpoint endpoint, in ResultReceiver callback);
+
void enterBackgroundAudioProcessing(String callId);
void exitBackgroundAudioProcessing(String callId, boolean shouldRing);
diff --git a/telecomm/java/com/android/internal/telecom/IInCallService.aidl b/telecomm/java/com/android/internal/telecom/IInCallService.aidl
index b9563fa..bac295a 100644
--- a/telecomm/java/com/android/internal/telecom/IInCallService.aidl
+++ b/telecomm/java/com/android/internal/telecom/IInCallService.aidl
@@ -19,6 +19,7 @@
import android.app.PendingIntent;
import android.os.Bundle;
import android.telecom.CallAudioState;
+import android.telecom.CallEndpoint;
import android.telecom.ParcelableCall;
import com.android.internal.telecom.IInCallAdapter;
@@ -43,6 +44,12 @@
void onCallAudioStateChanged(in CallAudioState callAudioState);
+ void onCallEndpointChanged(in CallEndpoint callEndpoint);
+
+ void onAvailableCallEndpointsChanged(in List<CallEndpoint> availableCallEndpoints);
+
+ void onMuteStateChanged(boolean isMuted);
+
void bringToForeground(boolean showDialpad);
void onCanAddCallChanged(boolean canAddCall);
diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java
index c4744ef..884458f 100644
--- a/telephony/java/android/telephony/CarrierConfigManager.java
+++ b/telephony/java/android/telephony/CarrierConfigManager.java
@@ -2990,6 +2990,39 @@
"5g_nr_sssinr_thresholds_int_array";
/**
+ * An interval in dB for {@link SignalThresholdInfo#SIGNAL_MEASUREMENT_TYPE_SSRSRP} measurement
+ * type defining the required magnitude change between reports.
+ *
+ * <p>The default value is 2 and the minimum allowed value is 0. If no value or negative value
+ * is set, the default value 2 is used.
+ * @hide
+ */
+ public static final String
+ KEY_NGRAN_SSRSRP_HYSTERESIS_DB_INT = "ngran_ssrsrp_hysteresis_db_int";
+
+ /**
+ * An interval in dB for {@link SignalThresholdInfo#SIGNAL_MEASUREMENT_TYPE_SSRSRQ} measurement
+ * type defining the required magnitude change between reports.
+ *
+ * <p>The default value is 2 and the minimum allowed value is 0. If no value or negative value
+ * is set, the default value 2 is used.
+ *@hide
+ */
+ public static final String
+ KEY_NGRAN_SSRSRQ_HYSTERESIS_DB_INT = "ngran_ssrsrq_hysteresis_db_int";
+
+ /**
+ * An interval in dB for {@link SignalThresholdInfo#SIGNAL_MEASUREMENT_TYPE_SSSINR} measurement
+ * type defining the required magnitude change between reports.
+ *
+ * <p>The default value is 2 and the minimum allowed value is 0. If no value or negative value
+ * is set, the default value 2 is used.
+ * @hide
+ */
+ public static final String
+ KEY_NGRAN_SSSINR_HYSTERESIS_DB_INT = "ngran_sssinr_hysteresis_db_int";
+
+ /**
* Bit-field integer to determine whether to use SS reference signal received power (SSRSRP),
* SS reference signal received quality (SSRSRQ), or/and SS signal-to-noise and interference
* ratio (SSSINR) for the number of 5G NR signal bars and signal criteria reporting enabling.
@@ -3326,6 +3359,38 @@
"lte_rssnr_thresholds_int_array";
/**
+ * An interval in dB for {@link SignalThresholdInfo#SIGNAL_MEASUREMENT_TYPE_RSRP} measurement
+ * type defining the required magnitude change between reports.
+ *
+ * <p>The default value is 2 and the minimum allowed value is 0. If no value or negative value
+ * is set, the default value 2 is used.
+ * @hide
+ */
+ public static final String
+ KEY_EUTRAN_RSRP_HYSTERESIS_DB_INT = "eutran_rsrp_hysteresis_db_int";
+
+ /**
+ * An interval in dB for {@link SignalThresholdInfo#SIGNAL_MEASUREMENT_TYPE_RSRQ} measurement
+ * type defining the required magnitude change between reports.
+ *
+ * <p>The default value is 2 and the minimum allowed value is 0. If no value or negative value
+ * is set, the default value 2 is used.
+ * @hide
+ */
+ public static final String KEY_EUTRAN_RSRQ_HYSTERESIS_DB_INT = "eutran_rsrq_hysteresis_db_int";
+
+ /**
+ * An interval in dB for {@link SignalThresholdInfo#SIGNAL_MEASUREMENT_TYPE_RSSNR} measurement
+ * type defining the required magnitude change between reports.
+ *
+ * <p>The default value is 2 and the minimum allowed value is 0. If no value or negative value
+ * is set, the default value 2 is used.
+ * @hide
+ */
+ public static final String
+ KEY_EUTRAN_RSSNR_HYSTERESIS_DB_INT = "eutran_rssnr_hysteresis_db_int";
+
+ /**
* Decides when clients try to bind to iwlan network service, which package name will
* the binding intent go to.
* @hide
@@ -3402,6 +3467,26 @@
"wcdma_ecno_thresholds_int_array";
/**
+ * An interval in dB for {@link SignalThresholdInfo#SIGNAL_MEASUREMENT_TYPE_RSCP} measurement
+ * type defining the required magnitude change between reports.
+ *
+ * <p>The default value is 2 and the minimum allowed value is 0. If no value or negative value
+ * is set, the default value 2 is used.
+ * @hide
+ */
+ public static final String KEY_UTRAN_RSCP_HYSTERESIS_DB_INT = "utran_rscp_hysteresis_db_int";
+
+ /**
+ * An interval in dB for {@link SignalThresholdInfo#SIGNAL_MEASUREMENT_TYPE_ECNO} measurement
+ * type defining the required magnitude change between reports.
+ *
+ * <p>The default value is 2 and the minimum allowed value is 0. If no value or negative value
+ * is set, the default value 2 is used.
+ * @hide
+ */
+ public static final String KEY_UTRAN_ECNO_HYSTERESIS_DB_INT = "utran_ecno_hysteresis_db_int";
+
+ /**
* The default measurement to use for signal strength reporting. If this is not specified, the
* RSSI is used.
* <p>
@@ -8686,6 +8771,16 @@
"gsm_rssi_thresholds_int_array";
/**
+ * An interval in dB for {@link SignalThresholdInfo#SIGNAL_MEASUREMENT_TYPE_RSSI} measurement
+ * type defining the required magnitude change between reports.
+ *
+ * <p>The default value is 2 and the minimum allowed value is 0. If no value or negative value
+ * is set, the default value 2 is used.
+ * @hide
+ */
+ public static final String KEY_GERAN_RSSI_HYSTERESIS_DB_INT = "geran_rssi_hysteresis_db_int";
+
+ /**
* Determines whether Wireless Priority Service call is supported over IMS.
*
* See Wireless Priority Service from https://www.fcc.gov/general/wireless-priority-service-wps
@@ -9666,6 +9761,15 @@
15, /* SIGNAL_STRENGTH_GOOD */
30 /* SIGNAL_STRENGTH_GREAT */
});
+ sDefaults.putInt(KEY_GERAN_RSSI_HYSTERESIS_DB_INT, 2);
+ sDefaults.putInt(KEY_UTRAN_RSCP_HYSTERESIS_DB_INT, 2);
+ sDefaults.putInt(KEY_EUTRAN_RSRP_HYSTERESIS_DB_INT, 2);
+ sDefaults.putInt(KEY_EUTRAN_RSRQ_HYSTERESIS_DB_INT, 2);
+ sDefaults.putInt(KEY_EUTRAN_RSSNR_HYSTERESIS_DB_INT, 2);
+ sDefaults.putInt(KEY_NGRAN_SSRSRP_HYSTERESIS_DB_INT, 2);
+ sDefaults.putInt(KEY_NGRAN_SSRSRQ_HYSTERESIS_DB_INT, 2);
+ sDefaults.putInt(KEY_NGRAN_SSSINR_HYSTERESIS_DB_INT, 2);
+ sDefaults.putInt(KEY_UTRAN_ECNO_HYSTERESIS_DB_INT, 2);
sDefaults.putInt(KEY_PARAMETERS_USE_FOR_5G_NR_SIGNAL_BAR_INT,
CellSignalStrengthNr.USE_SSRSRP);
sDefaults.putBoolean(KEY_SIGNAL_STRENGTH_NR_NSA_USE_LTE_AS_PRIMARY_BOOL, true);
diff --git a/telephony/java/android/telephony/DataSpecificRegistrationInfo.java b/telephony/java/android/telephony/DataSpecificRegistrationInfo.java
index ca6dc2d..5e5e028 100644
--- a/telephony/java/android/telephony/DataSpecificRegistrationInfo.java
+++ b/telephony/java/android/telephony/DataSpecificRegistrationInfo.java
@@ -16,6 +16,7 @@
package android.telephony;
+import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.SystemApi;
@@ -24,6 +25,8 @@
import com.android.internal.annotations.VisibleForTesting;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
import java.util.Objects;
@@ -33,6 +36,69 @@
*/
@SystemApi
public final class DataSpecificRegistrationInfo implements Parcelable {
+
+ /** @hide */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(
+ prefix = "LTE_ATTACH_TYPE_",
+ value = {
+ LTE_ATTACH_TYPE_UNKNOWN,
+ LTE_ATTACH_TYPE_EPS_ONLY,
+ LTE_ATTACH_TYPE_COMBINED,
+ })
+ public @interface LteAttachResultType {}
+
+ /**
+ * Default value.
+ * Attach type is unknown.
+ */
+ public static final int LTE_ATTACH_TYPE_UNKNOWN = 0;
+
+ /**
+ * LTE is attached with EPS only.
+ *
+ * Reference: 3GPP TS 24.301 9.9.3 EMM information elements.
+ */
+ public static final int LTE_ATTACH_TYPE_EPS_ONLY = 1;
+
+ /**
+ * LTE combined EPS and IMSI attach.
+ *
+ * Reference: 3GPP TS 24.301 9.9.3 EMM information elements.
+ */
+ public static final int LTE_ATTACH_TYPE_COMBINED = 2;
+
+ /** @hide */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(flag = true, prefix = {"LTE_ATTACH_EXTRA_INFO_"},
+ value = {
+ LTE_ATTACH_EXTRA_INFO_NONE,
+ LTE_ATTACH_EXTRA_INFO_CSFB_NOT_PREFERRED,
+ LTE_ATTACH_EXTRA_INFO_SMS_ONLY
+ })
+ public @interface LteAttachExtraInfo {}
+
+ /**
+ * Default value.
+ */
+ public static final int LTE_ATTACH_EXTRA_INFO_NONE = 0;
+
+ /**
+ * CSFB is not preferred.
+ * Applicable for LTE only.
+ *
+ * Reference: 3GPP TS 24.301 9.9.3 EMM information elements.
+ */
+ public static final int LTE_ATTACH_EXTRA_INFO_CSFB_NOT_PREFERRED = 1 << 0;
+
+ /**
+ * Attached for SMS only.
+ * Applicable for LTE only.
+ *
+ * Reference: 3GPP TS 24.301 9.9.3 EMM information elements.
+ */
+ public static final int LTE_ATTACH_EXTRA_INFO_SMS_ONLY = 1 << 1;
+
/**
* @hide
* The maximum number of simultaneous Data Calls that
@@ -75,6 +141,22 @@
@Nullable
private final VopsSupportInfo mVopsSupportInfo;
+ /** The type of network attachment */
+ private final @LteAttachResultType int mLteAttachResultType;
+
+ /** LTE attach extra info */
+ private final @LteAttachExtraInfo int mLteAttachExtraInfo;
+
+ private DataSpecificRegistrationInfo(Builder builder) {
+ this.maxDataCalls = builder.mMaxDataCalls;
+ this.isDcNrRestricted = builder.mIsDcNrRestricted;
+ this.isNrAvailable = builder.mIsNrAvailable;
+ this.isEnDcAvailable = builder.mIsEnDcAvailable;
+ this.mVopsSupportInfo = builder.mVopsSupportInfo;
+ this.mLteAttachResultType = builder.mLteAttachResultType;
+ this.mLteAttachExtraInfo = builder.mLteAttachExtraInfo;
+ }
+
/**
* @hide
*/
@@ -87,6 +169,8 @@
this.isNrAvailable = isNrAvailable;
this.isEnDcAvailable = isEnDcAvailable;
this.mVopsSupportInfo = vops;
+ this.mLteAttachResultType = LTE_ATTACH_TYPE_UNKNOWN;
+ this.mLteAttachExtraInfo = LTE_ATTACH_EXTRA_INFO_NONE;
}
/**
@@ -101,6 +185,8 @@
isNrAvailable = dsri.isNrAvailable;
isEnDcAvailable = dsri.isEnDcAvailable;
mVopsSupportInfo = dsri.mVopsSupportInfo;
+ mLteAttachResultType = dsri.mLteAttachResultType;
+ mLteAttachExtraInfo = dsri.mLteAttachExtraInfo;
}
private DataSpecificRegistrationInfo(/* @NonNull */ Parcel source) {
@@ -109,6 +195,8 @@
isNrAvailable = source.readBoolean();
isEnDcAvailable = source.readBoolean();
mVopsSupportInfo = source.readParcelable(VopsSupportInfo.class.getClassLoader(), android.telephony.VopsSupportInfo.class);
+ mLteAttachResultType = source.readInt();
+ mLteAttachExtraInfo = source.readInt();
}
@Override
@@ -118,6 +206,8 @@
dest.writeBoolean(isNrAvailable);
dest.writeBoolean(isEnDcAvailable);
dest.writeParcelable(mVopsSupportInfo, flags);
+ dest.writeInt(mLteAttachResultType);
+ dest.writeInt(mLteAttachExtraInfo);
}
@Override
@@ -134,6 +224,8 @@
.append(" isDcNrRestricted = " + isDcNrRestricted)
.append(" isNrAvailable = " + isNrAvailable)
.append(" isEnDcAvailable = " + isEnDcAvailable)
+ .append(" mLteAttachResultType = " + mLteAttachResultType)
+ .append(" mLteAttachExtraInfo = " + mLteAttachExtraInfo)
.append(" " + mVopsSupportInfo)
.append(" }")
.toString();
@@ -142,7 +234,8 @@
@Override
public int hashCode() {
return Objects.hash(maxDataCalls, isDcNrRestricted, isNrAvailable,
- isEnDcAvailable, mVopsSupportInfo);
+ isEnDcAvailable, mVopsSupportInfo,
+ mLteAttachResultType, mLteAttachExtraInfo);
}
@Override
@@ -156,7 +249,9 @@
&& this.isDcNrRestricted == other.isDcNrRestricted
&& this.isNrAvailable == other.isNrAvailable
&& this.isEnDcAvailable == other.isEnDcAvailable
- && Objects.equals(mVopsSupportInfo, other.mVopsSupportInfo);
+ && Objects.equals(mVopsSupportInfo, other.mVopsSupportInfo)
+ && this.mLteAttachResultType == other.mLteAttachResultType
+ && this.mLteAttachExtraInfo == other.mLteAttachExtraInfo;
}
public static final @NonNull Parcelable.Creator<DataSpecificRegistrationInfo> CREATOR =
@@ -196,4 +291,105 @@
public VopsSupportInfo getVopsSupportInfo() {
return mVopsSupportInfo;
}
+
+ /**
+ * Provides the LTE attach type.
+ */
+ public @LteAttachResultType int getLteAttachResultType() {
+ return mLteAttachResultType;
+ }
+
+ /**
+ * Provides the extra information of LTE attachment.
+ *
+ * @return the bitwise OR of {@link LteAttachExtraInfo}.
+ */
+ public @LteAttachExtraInfo int getLteAttachExtraInfo() {
+ return mLteAttachExtraInfo;
+ }
+
+ /**
+ * Builds {@link DataSpecificRegistrationInfo} instances, which may include optional parameters.
+ * @hide
+ */
+ public static final class Builder {
+ private final int mMaxDataCalls;
+
+ private boolean mIsDcNrRestricted;
+ private boolean mIsNrAvailable;
+ private boolean mIsEnDcAvailable;
+ private @Nullable VopsSupportInfo mVopsSupportInfo;
+ private @LteAttachResultType int mLteAttachResultType = LTE_ATTACH_TYPE_UNKNOWN;
+ private @LteAttachExtraInfo int mLteAttachExtraInfo = LTE_ATTACH_EXTRA_INFO_NONE;
+
+ public Builder(int maxDataCalls) {
+ mMaxDataCalls = maxDataCalls;
+ }
+
+ /**
+ * Ses whether the use of dual connectivity with NR is restricted.
+ * @param isDcNrRestricted {@code true} if the use of dual connectivity with NR is
+ * restricted.
+ */
+ public @NonNull Builder setDcNrRestricted(boolean isDcNrRestricted) {
+ mIsDcNrRestricted = isDcNrRestricted;
+ return this;
+ }
+
+ /**
+ * Sets whether NR is supported by the selected PLMN.
+ * @param isNrAvailable {@code true} if NR is supported.
+ */
+ public @NonNull Builder setNrAvailable(boolean isNrAvailable) {
+ mIsNrAvailable = isNrAvailable;
+ return this;
+ }
+
+ /**
+ * Sets whether E-UTRA-NR Dual Connectivity (EN-DC) is supported by the primary serving
+ * cell.
+ * @param isEnDcAvailable {@code true} if EN_DC is supported.
+ */
+ public @NonNull Builder setEnDcAvailable(boolean isEnDcAvailable) {
+ mIsEnDcAvailable = isEnDcAvailable;
+ return this;
+ }
+
+ /**
+ * Sets the network support info for VoPS and Emergency bearer support.
+ * @param vops The network support info for VoPS and Emergency bearer support.
+ */
+ @Nullable
+ public @NonNull Builder setVopsSupportInfo(VopsSupportInfo vops) {
+ mVopsSupportInfo = vops;
+ return this;
+ }
+
+ /**
+ * Sets the LTE attach type.
+ * @param lteAttachResultType the Lte attach type
+ */
+ public @NonNull Builder setLteAttachResultType(
+ @LteAttachResultType int lteAttachResultType) {
+ mLteAttachResultType = lteAttachResultType;
+ return this;
+ }
+
+ /**
+ * Sets the extra information of LTE attachment.
+ * @param lteAttachExtraInfo the extra information of LTE attachment.
+ */
+ public @NonNull Builder setLteAttachExtraInfo(
+ @LteAttachExtraInfo int lteAttachExtraInfo) {
+ mLteAttachExtraInfo = lteAttachExtraInfo;
+ return this;
+ }
+
+ /**
+ * @return a built {@link DataSpecificRegistrationInfo} instance.
+ */
+ public @NonNull DataSpecificRegistrationInfo build() {
+ return new DataSpecificRegistrationInfo(this);
+ }
+ }
}
diff --git a/telephony/java/android/telephony/NetworkRegistrationInfo.java b/telephony/java/android/telephony/NetworkRegistrationInfo.java
index a0467c2..cbd3df4 100644
--- a/telephony/java/android/telephony/NetworkRegistrationInfo.java
+++ b/telephony/java/android/telephony/NetworkRegistrationInfo.java
@@ -20,6 +20,9 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.SystemApi;
+import android.app.compat.CompatChanges;
+import android.compat.annotation.ChangeId;
+import android.compat.annotation.EnabledSince;
import android.os.Build;
import android.os.Parcel;
import android.os.Parcelable;
@@ -39,6 +42,16 @@
* Description of a mobile network registration info
*/
public final class NetworkRegistrationInfo implements Parcelable {
+
+ /**
+ * A new registration state, REGISTRATION_STATE_EMERGENCY, is added to
+ * {@link NetworkRegistrationInfo}. This change will affect the result of getRegistration().
+ * @hide
+ */
+ @ChangeId
+ @EnabledSince(targetSdkVersion = android.os.Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+ public static final long RETURN_REGISTRATION_STATE_EMERGENCY = 255938466L;
+
/**
* Network domain
* @hide
@@ -64,7 +77,8 @@
@IntDef(prefix = "REGISTRATION_STATE_",
value = {REGISTRATION_STATE_NOT_REGISTERED_OR_SEARCHING, REGISTRATION_STATE_HOME,
REGISTRATION_STATE_NOT_REGISTERED_SEARCHING, REGISTRATION_STATE_DENIED,
- REGISTRATION_STATE_UNKNOWN, REGISTRATION_STATE_ROAMING})
+ REGISTRATION_STATE_UNKNOWN, REGISTRATION_STATE_ROAMING,
+ REGISTRATION_STATE_EMERGENCY})
public @interface RegistrationState {}
/**
@@ -103,6 +117,18 @@
*/
@SystemApi
public static final int REGISTRATION_STATE_ROAMING = 5;
+ /**
+ * Emergency attached in EPS or in 5GS.
+ * IMS service will skip emergency registration if the device is in
+ * emergency attached state. {@link #mEmergencyOnly} can be true
+ * even in case it's not in emergency attached state.
+ *
+ * Reference: 3GPP TS 24.301 9.9.3.11 EPS attach type.
+ * Reference: 3GPP TS 24.501 9.11.3.6 5GS registration result.
+ * @hide
+ */
+ @SystemApi
+ public static final int REGISTRATION_STATE_EMERGENCY = 6;
/** @hide */
@Retention(RetentionPolicy.SOURCE)
@@ -312,8 +338,12 @@
@Nullable VopsSupportInfo vopsSupportInfo) {
this(domain, transportType, registrationState, accessNetworkTechnology, rejectCause,
emergencyOnly, availableServices, cellIdentity, rplmn, null,
- new DataSpecificRegistrationInfo(maxDataCalls, isDcNrRestricted, isNrAvailable,
- isEndcAvailable, vopsSupportInfo));
+ new DataSpecificRegistrationInfo.Builder(maxDataCalls)
+ .setDcNrRestricted(isDcNrRestricted)
+ .setNrAvailable(isNrAvailable)
+ .setEnDcAvailable(isEndcAvailable)
+ .setVopsSupportInfo(vopsSupportInfo)
+ .build());
}
private NetworkRegistrationInfo(Parcel source) {
@@ -406,6 +436,15 @@
*/
@SystemApi
public @RegistrationState int getRegistrationState() {
+ if (mRegistrationState == REGISTRATION_STATE_EMERGENCY) {
+ if (!CompatChanges.isChangeEnabled(RETURN_REGISTRATION_STATE_EMERGENCY)) {
+ if (mAccessNetworkTechnology == TelephonyManager.NETWORK_TYPE_LTE) {
+ return REGISTRATION_STATE_DENIED;
+ } else if (mAccessNetworkTechnology == TelephonyManager.NETWORK_TYPE_NR) {
+ return REGISTRATION_STATE_NOT_REGISTERED_OR_SEARCHING;
+ }
+ }
+ }
return mRegistrationState;
}
@@ -630,6 +669,7 @@
case REGISTRATION_STATE_DENIED: return "DENIED";
case REGISTRATION_STATE_UNKNOWN: return "UNKNOWN";
case REGISTRATION_STATE_ROAMING: return "ROAMING";
+ case REGISTRATION_STATE_EMERGENCY: return "EMERGENCY";
}
return "Unknown reg state " + registrationState;
}
diff --git a/telephony/java/android/telephony/SignalThresholdInfo.java b/telephony/java/android/telephony/SignalThresholdInfo.java
index 7053b44..80f1de1 100644
--- a/telephony/java/android/telephony/SignalThresholdInfo.java
+++ b/telephony/java/android/telephony/SignalThresholdInfo.java
@@ -17,6 +17,7 @@
package android.telephony;
import android.annotation.IntDef;
+import android.annotation.IntRange;
import android.annotation.NonNull;
import android.os.Parcel;
import android.os.Parcelable;
@@ -169,11 +170,18 @@
public static final int HYSTERESIS_MS_DISABLED = 0;
/**
- * Indicates the hysteresisDb is disabled.
+ * Indicates the default hysteresis value in dB.
*
* @hide
*/
- public static final int HYSTERESIS_DB_DISABLED = 0;
+ private static final int HYSTERESIS_DB_DEFAULT = 2;
+
+ /**
+ * Indicates the hysteresisDb value is not set and to be initialised to default value.
+ *
+ * @hide
+ */
+ public static final int HYSTERESIS_DB_MINIMUM = 0;
/**
* Minimum valid value for {@link #SIGNAL_MEASUREMENT_TYPE_RSSI}.
@@ -339,7 +347,7 @@
mRan = ran;
mSignalMeasurementType = signalMeasurementType;
mHysteresisMs = hysteresisMs < 0 ? HYSTERESIS_MS_DISABLED : hysteresisMs;
- mHysteresisDb = hysteresisDb < 0 ? HYSTERESIS_DB_DISABLED : hysteresisDb;
+ mHysteresisDb = hysteresisDb;
mThresholds = thresholds;
mIsEnabled = isEnabled;
}
@@ -351,7 +359,7 @@
private int mRan = AccessNetworkConstants.AccessNetworkType.UNKNOWN;
private int mSignalMeasurementType = SIGNAL_MEASUREMENT_TYPE_UNKNOWN;
private int mHysteresisMs = HYSTERESIS_MS_DISABLED;
- private int mHysteresisDb = HYSTERESIS_DB_DISABLED;
+ private int mHysteresisDb = HYSTERESIS_DB_DEFAULT;
private int[] mThresholds = null;
private boolean mIsEnabled = false;
@@ -361,7 +369,8 @@
* @param ran The radio access network type
* @return the builder to facilitate the chaining
*/
- public @NonNull Builder setRadioAccessNetworkType(
+ @NonNull
+ public Builder setRadioAccessNetworkType(
@AccessNetworkConstants.RadioAccessNetworkType int ran) {
mRan = ran;
return this;
@@ -373,7 +382,8 @@
* @param signalMeasurementType The signal measurement type
* @return the builder to facilitate the chaining
*/
- public @NonNull Builder setSignalMeasurementType(
+ @NonNull
+ public Builder setSignalMeasurementType(
@SignalMeasurementType int signalMeasurementType) {
mSignalMeasurementType = signalMeasurementType;
return this;
@@ -387,20 +397,27 @@
* @return the builder to facilitate the chaining
* @hide
*/
- public @NonNull Builder setHysteresisMs(int hysteresisMs) {
+ @NonNull
+ public Builder setHysteresisMs(int hysteresisMs) {
mHysteresisMs = hysteresisMs;
return this;
}
/**
- * Set the interval in dB defining the required magnitude change between reports. A value of
- * zero disabled dB-based hysteresis restrictions.
+ * Set the interval in dB defining the required minimum magnitude change to report a
+ * signal strength change. A value of zero disables dB-based hysteresis restrictions.
+ * Note:
+ * <p>Default hysteresis db value is 2. Minimum hysteresis db value allowed to set is 0.
+ * If hysteresis db value is not set, default hysteresis db value of 2 will be used.
*
* @param hysteresisDb the interval in dB
* @return the builder to facilitate the chaining
- * @hide
*/
- public @NonNull Builder setHysteresisDb(int hysteresisDb) {
+ @NonNull
+ public Builder setHysteresisDb(@IntRange(from = 0) int hysteresisDb) {
+ if (hysteresisDb < 0) {
+ throw new IllegalArgumentException("hysteresis db value should not be less than 0");
+ }
mHysteresisDb = hysteresisDb;
return this;
}
@@ -428,7 +445,8 @@
* @see #SIGNAL_MEASUREMENT_TYPE_ECNO
* @see #getThresholds() for more details on signal strength thresholds
*/
- public @NonNull Builder setThresholds(@NonNull int[] thresholds) {
+ @NonNull
+ public Builder setThresholds(@NonNull int[] thresholds) {
return setThresholds(thresholds, false /*isSystem*/);
}
@@ -442,7 +460,8 @@
*
* @hide
*/
- public @NonNull Builder setThresholds(@NonNull int[] thresholds, boolean isSystem) {
+ @NonNull
+ public Builder setThresholds(@NonNull int[] thresholds, boolean isSystem) {
Objects.requireNonNull(thresholds, "thresholds must not be null");
if (!isSystem
&& (thresholds.length < MINIMUM_NUMBER_OF_THRESHOLDS_ALLOWED
@@ -465,7 +484,8 @@
* @return the builder to facilitate the chaining
* @hide
*/
- public @NonNull Builder setIsEnabled(boolean isEnabled) {
+ @NonNull
+ public Builder setIsEnabled(boolean isEnabled) {
mIsEnabled = isEnabled;
return this;
}
@@ -479,7 +499,8 @@
* the thresholds is out of range, or the RAN is not allowed to set with the signal
* measurement type
*/
- public @NonNull SignalThresholdInfo build() {
+ @NonNull
+ public SignalThresholdInfo build() {
return new SignalThresholdInfo(
mRan,
mSignalMeasurementType,
@@ -495,7 +516,8 @@
*
* @return radio access network type
*/
- public @AccessNetworkConstants.RadioAccessNetworkType int getRadioAccessNetworkType() {
+ @AccessNetworkConstants.RadioAccessNetworkType
+ public int getRadioAccessNetworkType() {
return mRan;
}
@@ -504,7 +526,8 @@
*
* @return the SignalMeasurementType value
*/
- public @SignalMeasurementType int getSignalMeasurementType() {
+ @SignalMeasurementType
+ public int getSignalMeasurementType() {
return mSignalMeasurementType;
}
@@ -513,7 +536,11 @@
return mHysteresisMs;
}
- /** @hide */
+ /**
+ * Get measurement hysteresis db.
+ *
+ * @return hysteresis db value
+ */
public int getHysteresisDb() {
return mHysteresisDb;
}
@@ -544,7 +571,8 @@
* @see #SIGNAL_MEASUREMENT_TYPE_SSSINR
* @see #SIGNAL_MEASUREMENT_TYPE_ECNO
*/
- public @NonNull int[] getThresholds() {
+ @NonNull
+ public int[] getThresholds() {
return mThresholds.clone();
}
@@ -618,7 +646,8 @@
mIsEnabled);
}
- public static final @NonNull Parcelable.Creator<SignalThresholdInfo> CREATOR =
+ @NonNull
+ public static final Parcelable.Creator<SignalThresholdInfo> CREATOR =
new Parcelable.Creator<SignalThresholdInfo>() {
@Override
public SignalThresholdInfo createFromParcel(Parcel in) {
diff --git a/telephony/java/android/telephony/SubscriptionManager.java b/telephony/java/android/telephony/SubscriptionManager.java
index 8e8755d..4afc943 100644
--- a/telephony/java/android/telephony/SubscriptionManager.java
+++ b/telephony/java/android/telephony/SubscriptionManager.java
@@ -1730,31 +1730,30 @@
}
/**
- * Get all subscription info records from SIMs that are inserted now or were inserted before.
+ * Get all subscription info records from SIMs that are inserted now or previously inserted.
*
* <p>
* If the caller does not have {@link Manifest.permission#READ_PHONE_NUMBERS} permission,
* {@link SubscriptionInfo#getNumber()} will return empty string.
* If the caller does not have {@link Manifest.permission#USE_ICC_AUTH_WITH_DEVICE_IDENTIFIER},
- * {@link SubscriptionInfo#getIccId()} and {@link SubscriptionInfo#getCardString()} will return
- * empty string, and {@link SubscriptionInfo#getGroupUuid()} will return {@code null}.
+ * {@link SubscriptionInfo#getIccId()} will return an empty string, and
+ * {@link SubscriptionInfo#getGroupUuid()} will return {@code null}.
*
* <p>
- * The carrier app will always have full {@link SubscriptionInfo} for the subscriptions
- * that it has carrier privilege.
+ * The carrier app will only get the list of subscriptions that it has carrier privilege on,
+ * but will have non-stripped {@link SubscriptionInfo} in the list.
*
* @return List of all {@link SubscriptionInfo} records from SIMs that are inserted or
- * inserted before. Sorted by {@link SubscriptionInfo#getSimSlotIndex()}, then
+ * previously inserted. Sorted by {@link SubscriptionInfo#getSimSlotIndex()}, then
* {@link SubscriptionInfo#getSubscriptionId()}.
*
- * @hide
+ * @throws SecurityException if callers do not hold the required permission.
*/
+ @NonNull
@RequiresPermission(anyOf = {
Manifest.permission.READ_PHONE_STATE,
- Manifest.permission.READ_PRIVILEGED_PHONE_STATE,
"carrier privileges",
})
- @NonNull
public List<SubscriptionInfo> getAllSubscriptionInfoList() {
List<SubscriptionInfo> result = null;
try {
@@ -2228,13 +2227,23 @@
}
/**
- * Get an array of Subscription Ids for specified slot Index.
- * @param slotIndex the slot index.
- * @return subscription Ids or null if the given slot Index is not valid or there are no active
- * subscriptions in the slot.
+ * Get an array of subscription ids for specified logical SIM slot Index.
+ *
+ * @param slotIndex The logical SIM slot index.
+ *
+ * @return subscription Ids or {@code null} if the given slot index is not valid or there are
+ * no active subscription in the slot. In the implementation today, there will be no more
+ * than one subscriptions per logical SIM slot.
+ *
+ * @deprecated Use {@link #getSubscriptionId(int)} instead.
*/
+ @Deprecated
@Nullable
public int[] getSubscriptionIds(int slotIndex) {
+ int subId = getSubscriptionId(slotIndex);
+ if (!isValidSubscriptionId(subId)) {
+ return null;
+ }
return new int[]{getSubscriptionId(slotIndex)};
}
@@ -2261,12 +2270,10 @@
}
/**
- * Get the subscription id for specified slot index.
+ * Get the subscription id for specified logical SIM slot index.
*
- * @param slotIndex Logical SIM slot index.
+ * @param slotIndex The logical SIM slot index.
* @return The subscription id. {@link #INVALID_SUBSCRIPTION_ID} if SIM is absent.
- *
- * @hide
*/
public static int getSubscriptionId(int slotIndex) {
if (!isValidSlotIndex(slotIndex)) {
diff --git a/telephony/java/android/telephony/ims/ImsCallSession.java b/telephony/java/android/telephony/ims/ImsCallSession.java
index d3d94b4..98ed1fa 100755
--- a/telephony/java/android/telephony/ims/ImsCallSession.java
+++ b/telephony/java/android/telephony/ims/ImsCallSession.java
@@ -520,6 +520,14 @@
@NonNull Set<RtpHeaderExtension> extensions) {
// no-op
}
+
+ /**
+ * Called when radio to send ANBRQ message to the access network to query the desired
+ * bitrate.
+ */
+ public void callSessionSendAnbrQuery(int mediaType, int direction, int bitsPerSecond) {
+ // no-op
+ }
}
private final IImsCallSession miSession;
@@ -1224,6 +1232,27 @@
}
/**
+ * Deliver the bitrate for the indicated media type, direction and bitrate to the upper layer.
+ *
+ * @param mediaType MediaType is used to identify media stream such as audio or video.
+ * @param direction Direction of this packet stream (e.g. uplink or downlink).
+ * @param bitsPerSecond This value is the bitrate received from the NW through the Recommended
+ * bitrate MAC Control Element message and ImsStack converts this value from MAC bitrate
+ * to audio/video codec bitrate (defined in TS26.114).
+ */
+ public void callSessionNotifyAnbr(int mediaType, int direction, int bitsPerSecond) {
+ if (mClosed) {
+ return;
+ }
+
+ try {
+ miSession.callSessionNotifyAnbr(mediaType, direction, bitsPerSecond);
+ } catch (RemoteException e) {
+ Log.e(TAG, "callSessionNotifyAnbr" + e);
+ }
+ }
+
+ /**
* A listener type for receiving notification on IMS call session events.
* When an event is generated for an {@link IImsCallSession},
* the application is notified by having one of the methods called on
@@ -1716,6 +1745,26 @@
}
}, mListenerExecutor);
}
+
+ /**
+ * ANBR Query received.
+ *
+ * @param mediaType MediaType is used to identify media stream such as audio or video.
+ * @param direction Direction of this packet stream (e.g. uplink or downlink).
+ * @param bitsPerSecond This value is the bitrate requested by the other party UE through
+ * RTP CMR, RTCPAPP or TMMBR, and ImsStack converts this value to the MAC bitrate
+ * (defined in TS36.321, range: 0 ~ 8000 kbit/s).
+ */
+ @Override
+ public void callSessionSendAnbrQuery(int mediaType, int direction,
+ int bitsPerSecond) {
+ Log.d(TAG, "callSessionSendAnbrQuery in ImsCallSession");
+ TelephonyUtils.runWithCleanCallingIdentity(()-> {
+ if (mListener != null) {
+ mListener.callSessionSendAnbrQuery(mediaType, direction, bitsPerSecond);
+ }
+ }, mListenerExecutor);
+ }
}
/**
diff --git a/telephony/java/android/telephony/ims/ImsCallSessionListener.java b/telephony/java/android/telephony/ims/ImsCallSessionListener.java
index db99acf..93cea25 100644
--- a/telephony/java/android/telephony/ims/ImsCallSessionListener.java
+++ b/telephony/java/android/telephony/ims/ImsCallSessionListener.java
@@ -16,6 +16,8 @@
package android.telephony.ims;
+import android.annotation.IntDef;
+import android.annotation.IntRange;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.SystemApi;
@@ -25,9 +27,12 @@
import android.telephony.ServiceState;
import android.telephony.ims.aidl.IImsCallSessionListener;
import android.telephony.ims.stub.ImsCallSessionImplBase;
+import android.util.Log;
import com.android.ims.internal.IImsCallSession;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
import java.util.Objects;
import java.util.Set;
@@ -44,7 +49,7 @@
// ImsCallSessionListenerConverter is also changed.
@SystemApi
public class ImsCallSessionListener {
-
+ private static final String TAG = "ImsCallSessionListener";
private final IImsCallSessionListener mListener;
/** @hide */
@@ -808,5 +813,69 @@
e.rethrowFromSystemServer();
}
}
+
+ /** @hide */
+ @IntDef(flag = true,
+ value = {
+ MEDIA_STREAM_TYPE_AUDIO,
+ MEDIA_STREAM_TYPE_VIDEO,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface MediaStreamType {}
+
+ /**
+ * Media Stream Type - Audio
+ * @hide
+ */
+ public static final int MEDIA_STREAM_TYPE_AUDIO = 1;
+ /**
+ * Media Stream Type - Video
+ * @hide
+ */
+ public static final int MEDIA_STREAM_TYPE_VIDEO = 2;
+
+ /** @hide */
+ @IntDef(flag = true,
+ value = {
+ MEDIA_STREAM_DIRECTION_UPLINK,
+ MEDIA_STREAM_DIRECTION_DOWNLINK,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface MediaStreamDirection {}
+
+ /**
+ * Media Stream Direction - Uplink
+ * @hide
+ */
+ public static final int MEDIA_STREAM_DIRECTION_UPLINK = 1;
+ /**
+ * Media Stream Direction - Downlink
+ * @hide
+ */
+ public static final int MEDIA_STREAM_DIRECTION_DOWNLINK = 2;
+
+ /**
+ * Access Network Bitrate Recommendation Query (ANBRQ), see 3GPP TS 26.114.
+ * This API triggers radio to send ANBRQ message to the access network to query the
+ * desired bitrate.
+ *
+ * @param mediaType {@link MediaStreamType} is used to identify media stream such as
+ * audio or video.
+ * @param direction {@link MediaStreamDirection} of this packet stream (e.g. uplink
+ * or downlink).
+ * @param bitsPerSecond This value is the bitrate requested by the other party UE through
+ * RTP CMR, RTCPAPP or TMMBR, and ImsStack converts this value to the MAC bitrate
+ * (defined in TS36.321, range: 0 ~ 8000 kbit/s).
+ * @hide
+ */
+ public final void callSessionSendAnbrQuery(@MediaStreamType int mediaType,
+ @MediaStreamDirection int direction, @IntRange(from = 0) int bitsPerSecond) {
+ Log.d(TAG, "callSessionSendAnbrQuery in imscallsessonListener");
+ try {
+ mListener.callSessionSendAnbrQuery(mediaType, direction, bitsPerSecond);
+ } catch (RemoteException e) {
+ e.rethrowFromSystemServer();
+ }
+ }
}
diff --git a/telephony/java/android/telephony/ims/aidl/IImsCallSessionListener.aidl b/telephony/java/android/telephony/ims/aidl/IImsCallSessionListener.aidl
index ed03752..b58a5c79b 100644
--- a/telephony/java/android/telephony/ims/aidl/IImsCallSessionListener.aidl
+++ b/telephony/java/android/telephony/ims/aidl/IImsCallSessionListener.aidl
@@ -171,4 +171,17 @@
* @param extensions the RTP header extensions received.
*/
void callSessionRtpHeaderExtensionsReceived(in List<RtpHeaderExtension> extensions);
+
+ /**
+ * Access Network Bitrate Recommendation Query (ANBRQ), see 3GPP TS 26.114.
+ * This API triggers radio to send ANBRQ message to the access network to query the desired
+ * bitrate.
+ *
+ * @param mediaType MediaType is used to identify media stream such as audio or video.
+ * @param direction Direction of this packet stream (e.g. uplink or downlink).
+ * @param bitsPerSecond This value is the bitrate requested by the other party UE
+ * through RTP CMR, RTCPAPP or TMMBR, and ImsStack converts this value to the MAC bitrate
+ * (defined in TS36.321, range: 0 ~ 8000 kbit/s).
+ */
+ void callSessionSendAnbrQuery(int mediaType, int direction, int bitsPerSecond);
}
diff --git a/telephony/java/android/telephony/ims/compat/stub/ImsCallSessionImplBase.java b/telephony/java/android/telephony/ims/compat/stub/ImsCallSessionImplBase.java
index 8dcd711..798e801 100755
--- a/telephony/java/android/telephony/ims/compat/stub/ImsCallSessionImplBase.java
+++ b/telephony/java/android/telephony/ims/compat/stub/ImsCallSessionImplBase.java
@@ -417,6 +417,21 @@
}
/**
+ * Deliver the bitrate for the indicated media type, direction and bitrate to the upper layer.
+ *
+ * @param mediaType MediaType is used to identify media stream such as audio or video.
+ * @param direction Direction of this packet stream (e.g. uplink or downlink).
+ * @param bitsPerSecond This value is the bitrate received from the NW through the Recommended
+ * bitrate MAC Control Element message and ImsStack converts this value from MAC bitrate
+ * to audio/video codec bitrate (defined in TS26.114).
+ * @hide
+ */
+ @Override
+ public void callSessionNotifyAnbr(int mediaType, int direction, int bitsPerSecond) {
+ // no-op; not supported in compat layer.
+ }
+
+ /**
* There are two different ImsCallSessionListeners that need to reconciled here, we need to
* convert the "old" version of the com.android.ims.internal.IImsCallSessionListener to the
* "new" version of the Listener android.telephony.ims.ImsCallSessionListener when calling
@@ -662,5 +677,11 @@
public void callQualityChanged(CallQuality callQuality) throws RemoteException {
mNewListener.callQualityChanged(callQuality);
}
+
+ @Override
+ public void callSessionSendAnbrQuery(int mediaType, int direction,
+ int bitsPerSecond) throws RemoteException {
+ mNewListener.callSessionSendAnbrQuery(mediaType, direction, bitsPerSecond);
+ }
}
}
diff --git a/telephony/java/android/telephony/ims/feature/MmTelFeature.java b/telephony/java/android/telephony/ims/feature/MmTelFeature.java
index e213588..f412116 100644
--- a/telephony/java/android/telephony/ims/feature/MmTelFeature.java
+++ b/telephony/java/android/telephony/ims/feature/MmTelFeature.java
@@ -84,10 +84,12 @@
private static final String LOG_TAG = "MmTelFeature";
private Executor mExecutor;
+ private ImsSmsImplBase mSmsImpl;
private HashMap<ImsTrafficSessionCallback, ImsTrafficSessionCallbackWrapper> mTrafficCallbacks =
new HashMap<>();
/**
+ * Creates a new MmTelFeature using the Executor set in {@link ImsService#getExecutor}
* @hide
*/
@SystemApi
@@ -269,50 +271,54 @@
@Override
public void setSmsListener(IImsSmsListener l) {
executeMethodAsyncNoException(() -> MmTelFeature.this.setSmsListener(l),
- "setSmsListener");
+ "setSmsListener", getImsSmsImpl().getExecutor());
}
@Override
public void sendSms(int token, int messageRef, String format, String smsc, boolean retry,
byte[] pdu) {
executeMethodAsyncNoException(() -> MmTelFeature.this
- .sendSms(token, messageRef, format, smsc, retry, pdu), "sendSms");
+ .sendSms(token, messageRef, format, smsc, retry, pdu), "sendSms",
+ getImsSmsImpl().getExecutor());
}
@Override
public void onMemoryAvailable(int token) {
executeMethodAsyncNoException(() -> MmTelFeature.this
- .onMemoryAvailable(token), "onMemoryAvailable");
+ .onMemoryAvailable(token), "onMemoryAvailable", getImsSmsImpl().getExecutor());
}
@Override
public void acknowledgeSms(int token, int messageRef, int result) {
executeMethodAsyncNoException(() -> MmTelFeature.this
- .acknowledgeSms(token, messageRef, result), "acknowledgeSms");
+ .acknowledgeSms(token, messageRef, result), "acknowledgeSms",
+ getImsSmsImpl().getExecutor());
}
@Override
public void acknowledgeSmsWithPdu(int token, int messageRef, int result, byte[] pdu) {
executeMethodAsyncNoException(() -> MmTelFeature.this
- .acknowledgeSms(token, messageRef, result, pdu), "acknowledgeSms");
+ .acknowledgeSms(token, messageRef, result, pdu), "acknowledgeSms",
+ getImsSmsImpl().getExecutor());
}
@Override
public void acknowledgeSmsReport(int token, int messageRef, int result) {
executeMethodAsyncNoException(() -> MmTelFeature.this
- .acknowledgeSmsReport(token, messageRef, result), "acknowledgeSmsReport");
+ .acknowledgeSmsReport(token, messageRef, result), "acknowledgeSmsReport",
+ getImsSmsImpl().getExecutor());
}
@Override
public String getSmsFormat() {
return executeMethodAsyncForResultNoException(() -> MmTelFeature.this
- .getSmsFormat(), "getSmsFormat");
+ .getSmsFormat(), "getSmsFormat", getImsSmsImpl().getExecutor());
}
@Override
public void onSmsReady() {
executeMethodAsyncNoException(() -> MmTelFeature.this.onSmsReady(),
- "onSmsReady");
+ "onSmsReady", getImsSmsImpl().getExecutor());
}
@Override
@@ -347,6 +353,19 @@
() -> MmTelFeature.this.notifySrvccCanceled(), "notifySrvccCanceled");
}
+ @Override
+ public void setTerminalBasedCallWaitingStatus(boolean enabled) throws RemoteException {
+ synchronized (mLock) {
+ try {
+ MmTelFeature.this.setTerminalBasedCallWaitingStatus(enabled);
+ } catch (ServiceSpecificException se) {
+ throw new ServiceSpecificException(se.errorCode, se.getMessage());
+ } catch (Exception e) {
+ throw new RemoteException(e.getMessage());
+ }
+ }
+ }
+
// Call the methods with a clean calling identity on the executor and wait indefinitely for
// the future to return.
private void executeMethodAsync(Runnable r, String errorLogName) throws RemoteException {
@@ -370,6 +389,17 @@
}
}
+ private void executeMethodAsyncNoException(Runnable r, String errorLogName,
+ Executor executor) {
+ try {
+ CompletableFuture.runAsync(
+ () -> TelephonyUtils.runWithCleanCallingIdentity(r), executor).join();
+ } catch (CancellationException | CompletionException e) {
+ Log.w(LOG_TAG, "MmTelFeature Binder - " + errorLogName + " exception: "
+ + e.getMessage());
+ }
+ }
+
private <T> T executeMethodAsyncForResult(Supplier<T> r,
String errorLogName) throws RemoteException {
CompletableFuture<T> future = CompletableFuture.supplyAsync(
@@ -396,16 +426,16 @@
}
}
- @Override
- public void setTerminalBasedCallWaitingStatus(boolean enabled) throws RemoteException {
- synchronized (mLock) {
- try {
- MmTelFeature.this.setTerminalBasedCallWaitingStatus(enabled);
- } catch (ServiceSpecificException se) {
- throw new ServiceSpecificException(se.errorCode, se.getMessage());
- } catch (Exception e) {
- throw new RemoteException(e.getMessage());
- }
+ private <T> T executeMethodAsyncForResultNoException(Supplier<T> r,
+ String errorLogName, Executor executor) {
+ CompletableFuture<T> future = CompletableFuture.supplyAsync(
+ () -> TelephonyUtils.runWithCleanCallingIdentity(r), executor);
+ try {
+ return future.get();
+ } catch (ExecutionException | InterruptedException e) {
+ Log.w(LOG_TAG, "MmTelFeature Binder - " + errorLogName + " exception: "
+ + e.getMessage());
+ return null;
}
}
};
@@ -1491,6 +1521,19 @@
}
/**
+ * @hide
+ */
+ public @NonNull ImsSmsImplBase getImsSmsImpl() {
+ synchronized (mLock) {
+ if (mSmsImpl == null) {
+ mSmsImpl = getSmsImplementation();
+ mSmsImpl.setDefaultExecutor(mExecutor);
+ }
+ return mSmsImpl;
+ }
+ }
+
+ /**
* @return The {@link ImsUtImplBase} Ut interface implementation for the supplementary service
* configuration.
* @hide
@@ -1638,35 +1681,35 @@
}
private void setSmsListener(IImsSmsListener listener) {
- getSmsImplementation().registerSmsListener(listener);
+ getImsSmsImpl().registerSmsListener(listener);
}
private void sendSms(int token, int messageRef, String format, String smsc, boolean isRetry,
byte[] pdu) {
- getSmsImplementation().sendSms(token, messageRef, format, smsc, isRetry, pdu);
+ getImsSmsImpl().sendSms(token, messageRef, format, smsc, isRetry, pdu);
}
private void onMemoryAvailable(int token) {
- getSmsImplementation().onMemoryAvailable(token);
+ getImsSmsImpl().onMemoryAvailable(token);
}
private void acknowledgeSms(int token, int messageRef,
@ImsSmsImplBase.DeliverStatusResult int result) {
- getSmsImplementation().acknowledgeSms(token, messageRef, result);
+ getImsSmsImpl().acknowledgeSms(token, messageRef, result);
}
private void acknowledgeSms(int token, int messageRef,
@ImsSmsImplBase.DeliverStatusResult int result, byte[] pdu) {
- getSmsImplementation().acknowledgeSms(token, messageRef, result, pdu);
+ getImsSmsImpl().acknowledgeSms(token, messageRef, result, pdu);
}
private void acknowledgeSmsReport(int token, int messageRef,
@ImsSmsImplBase.StatusReportResult int result) {
- getSmsImplementation().acknowledgeSmsReport(token, messageRef, result);
+ getImsSmsImpl().acknowledgeSmsReport(token, messageRef, result);
}
private void onSmsReady() {
- getSmsImplementation().onReady();
+ getImsSmsImpl().onReady();
}
/**
@@ -1683,7 +1726,7 @@
}
private String getSmsFormat() {
- return getSmsImplementation().getSmsFormat();
+ return getImsSmsImpl().getSmsFormat();
}
/**
diff --git a/telephony/java/android/telephony/ims/stub/ImsCallSessionImplBase.java b/telephony/java/android/telephony/ims/stub/ImsCallSessionImplBase.java
index f46938a..e46351dc 100644
--- a/telephony/java/android/telephony/ims/stub/ImsCallSessionImplBase.java
+++ b/telephony/java/android/telephony/ims/stub/ImsCallSessionImplBase.java
@@ -16,6 +16,8 @@
package android.telephony.ims.stub;
+import android.annotation.IntDef;
+import android.annotation.IntRange;
import android.annotation.NonNull;
import android.annotation.SystemApi;
import android.os.Bundle;
@@ -37,6 +39,8 @@
import com.android.ims.internal.IImsVideoCallProvider;
import com.android.internal.telephony.util.TelephonyUtils;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
import java.util.List;
import java.util.Set;
import java.util.concurrent.CancellationException;
@@ -68,6 +72,46 @@
*/
public static final int USSD_MODE_REQUEST = 1;
+ /** @hide */
+ @IntDef(
+ value = {
+ MEDIA_STREAM_TYPE_AUDIO,
+ MEDIA_STREAM_TYPE_VIDEO
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface MediaStreamType {}
+
+ /**
+ * Media Stream Type - Audio
+ * @hide
+ */
+ public static final int MEDIA_STREAM_TYPE_AUDIO = 1;
+ /**
+ * Media Stream Type - Video
+ * @hide
+ */
+ public static final int MEDIA_STREAM_TYPE_VIDEO = 2;
+
+ /** @hide */
+ @IntDef(
+ value = {
+ MEDIA_STREAM_DIRECTION_UPLINK,
+ MEDIA_STREAM_DIRECTION_DOWNLINK
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface MediaStreamDirection {}
+
+ /**
+ * Media Stream Direction - Uplink
+ * @hide
+ */
+ public static final int MEDIA_STREAM_DIRECTION_UPLINK = 1;
+ /**
+ * Media Stream Direction - Downlink
+ * @hide
+ */
+ public static final int MEDIA_STREAM_DIRECTION_DOWNLINK = 2;
+
/**
* Defines IMS call session state.
*/
@@ -327,6 +371,12 @@
new ArraySet<RtpHeaderExtension>(extensions)), "sendRtpHeaderExtensions");
}
+ @Override
+ public void callSessionNotifyAnbr(int mediaType, int direction, int bitsPerSecond) {
+ executeMethodAsync(() -> ImsCallSessionImplBase.this.callSessionNotifyAnbr(
+ mediaType, direction, bitsPerSecond), "callSessionNotifyAnbr");
+ }
+
// Call the methods with a clean calling identity on the executor and wait indefinitely for
// the future to return.
private void executeMethodAsync(Runnable r, String errorLogName) {
@@ -730,6 +780,22 @@
public void sendRtpHeaderExtensions(@NonNull Set<RtpHeaderExtension> rtpHeaderExtensions) {
}
+ /**
+ * Deliver the bitrate for the indicated media type, direction and bitrate to the upper layer.
+ *
+ * @param mediaType MediaType is used to identify media stream such as audio or video.
+ * @param direction Direction of this packet stream (e.g. uplink or downlink).
+ * @param bitsPerSecond This value is the bitrate received from the NW through the Recommended
+ * bitrate MAC Control Element message and ImsStack converts this value from MAC bitrate
+ * to audio/video codec bitrate (defined in TS26.114).
+ * @hide
+ */
+ public void callSessionNotifyAnbr(@MediaStreamType int mediaType,
+ @MediaStreamDirection int direction, @IntRange(from = 0) int bitsPerSecond) {
+ // Base Implementation - Should be overridden by IMS service
+ Log.i(LOG_TAG, "ImsCallSessionImplBase callSessionNotifyAnbr - mediaType: " + mediaType);
+ }
+
/** @hide */
public IImsCallSession getServiceImpl() {
return mServiceImpl;
diff --git a/telephony/java/android/telephony/ims/stub/ImsSmsImplBase.java b/telephony/java/android/telephony/ims/stub/ImsSmsImplBase.java
index daab84e..17cb3b4 100644
--- a/telephony/java/android/telephony/ims/stub/ImsSmsImplBase.java
+++ b/telephony/java/android/telephony/ims/stub/ImsSmsImplBase.java
@@ -28,6 +28,7 @@
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
+import java.util.concurrent.Executor;
/**
* Base implementation for SMS over IMS.
@@ -129,6 +130,22 @@
// Lock for feature synchronization
private final Object mLock = new Object();
private IImsSmsListener mListener;
+ private Executor mExecutor;
+
+ /**
+ * Create a new ImsSmsImplBase using the Executor set in MmTelFeature
+ */
+ public ImsSmsImplBase() {
+ }
+
+ /**
+ * Create a new ImsSmsImplBase with specified executor.
+ * <p>
+ * @param executor Default executor for ImsSmsImplBase
+ */
+ public ImsSmsImplBase(@NonNull Executor executor) {
+ mExecutor = executor;
+ }
/**
* Registers a listener responsible for handling tasks like delivering messages.
@@ -482,4 +499,29 @@
public void onReady() {
// Base Implementation - Should be overridden
}
+
+ /**
+ * Set default Executor for ImsSmsImplBase.
+ *
+ * @param executor The default executor for the framework to use when executing the methods
+ * overridden by the implementation of ImsSms.
+ * @hide
+ */
+ public final void setDefaultExecutor(@NonNull Executor executor) {
+ if (mExecutor == null) {
+ mExecutor = executor;
+ }
+ }
+
+ /**
+ * Get Executor from ImsSmsImplBase.
+ * If there is no settings for the executor, all ImsSmsImplBase method calls will use
+ * Runnable::run as default
+ *
+ * @return an Executor used to execute methods in ImsSms called remotely by the framework.
+ * @hide
+ */
+ public @NonNull Executor getExecutor() {
+ return mExecutor != null ? mExecutor : Runnable::run;
+ }
}
diff --git a/telephony/java/com/android/ims/internal/IImsCallSession.aidl b/telephony/java/com/android/ims/internal/IImsCallSession.aidl
index d7cf282..60a7478 100644
--- a/telephony/java/com/android/ims/internal/IImsCallSession.aidl
+++ b/telephony/java/com/android/ims/internal/IImsCallSession.aidl
@@ -307,4 +307,15 @@
* @param extensions the header extensions to be sent
*/
void sendRtpHeaderExtensions(in List<RtpHeaderExtension> extensions);
+
+ /*
+ * Deliver the bitrate for the indicated media type, direction and bitrate to the upper layer.
+ *
+ * @param mediaType MediaType is used to identify media stream such as audio or video.
+ * @param direction Direction of this packet stream (e.g. uplink or downlink).
+ * @param bitsPerSecond This value is the bitrate received from the NW through the Recommended
+ * bitrate MAC Control Element message and ImsStack converts this value from MAC bitrate
+ * to audio/video codec bitrate (defined in TS26.114).
+ */
+ void callSessionNotifyAnbr(int mediaType, int direction, int bitsPerSecond);
}
diff --git a/telephony/java/com/android/ims/internal/IImsCallSessionListener.aidl b/telephony/java/com/android/ims/internal/IImsCallSessionListener.aidl
index 8afd856..9395fbd 100644
--- a/telephony/java/com/android/ims/internal/IImsCallSessionListener.aidl
+++ b/telephony/java/com/android/ims/internal/IImsCallSessionListener.aidl
@@ -194,4 +194,17 @@
* @param callQuality then updated call quality
*/
void callQualityChanged(in CallQuality callQuality);
+
+ /**
+ * Access Network Bitrate Recommendation Query (ANBRQ), see 3GPP TS 26.114.
+ * This API triggers radio to send ANBRQ message to the access network to query the desired
+ * bitrate.
+ *
+ * @param mediaType MediaType is used to identify media stream such as audio or video.
+ * @param direction Direction of this packet stream (e.g. uplink or downlink).
+ * @param bitsPerSecond This value is the bitrate requested by the other party UE through
+ * RTP CMR, RTCPAPP or TMMBR, and ImsStack converts this value to the MAC bitrate
+ * (defined in TS36.321, range: 0 ~ 8000 kbit/s).
+ */
+ void callSessionSendAnbrQuery(int mediaType, int direction, int bitsPerSecond);
}
diff --git a/tests/UpdatableSystemFontTest/src/com/android/updatablesystemfont/UpdatableSystemFontTest.java b/tests/UpdatableSystemFontTest/src/com/android/updatablesystemfont/UpdatableSystemFontTest.java
index 650686f..fa5b7c1 100644
--- a/tests/UpdatableSystemFontTest/src/com/android/updatablesystemfont/UpdatableSystemFontTest.java
+++ b/tests/UpdatableSystemFontTest/src/com/android/updatablesystemfont/UpdatableSystemFontTest.java
@@ -69,6 +69,7 @@
import java.util.Collections;
import java.util.List;
import java.util.regex.Pattern;
+import java.util.stream.Stream;
/**
* Tests if fonts can be updated by {@link FontManager} API.
@@ -246,7 +247,10 @@
@Test
public void updateFontFamily() throws Exception {
assertThat(updateNotoSerifAs("serif")).isEqualTo(FontManager.RESULT_SUCCESS);
- FontConfig.FontFamily family = findFontFamilyOrThrow("serif");
+ final FontConfig.NamedFamilyList namedFamilyList = findFontFamilyOrThrow("serif");
+ assertThat(namedFamilyList.getFamilies().size()).isEqualTo(1);
+ final FontConfig.FontFamily family = namedFamilyList.getFamilies().get(0);
+
assertThat(family.getFontList()).hasSize(2);
assertThat(family.getFontList().get(0).getPostScriptName())
.isEqualTo(NOTO_SERIF_REGULAR_POSTSCRIPT_NAME);
@@ -265,7 +269,10 @@
public void updateFontFamily_asNewFont() throws Exception {
assertThat(updateNotoSerifAs("UpdatableSystemFontTest-serif"))
.isEqualTo(FontManager.RESULT_SUCCESS);
- FontConfig.FontFamily family = findFontFamilyOrThrow("UpdatableSystemFontTest-serif");
+ final FontConfig.NamedFamilyList namedFamilyList =
+ findFontFamilyOrThrow("UpdatableSystemFontTest-serif");
+ assertThat(namedFamilyList.getFamilies().size()).isEqualTo(1);
+ final FontConfig.FontFamily family = namedFamilyList.getFamilies().get(0);
assertThat(family.getFontList()).hasSize(2);
assertThat(family.getFontList().get(0).getPostScriptName())
.isEqualTo(NOTO_SERIF_REGULAR_POSTSCRIPT_NAME);
@@ -434,9 +441,15 @@
private String getFontPath(String psName) {
FontConfig fontConfig =
SystemUtil.runWithShellPermissionIdentity(mFontManager::getFontConfig);
- return fontConfig.getFontFamilies().stream()
+ final List<FontConfig.FontFamily> namedFamilies = fontConfig.getNamedFamilyLists().stream()
+ .flatMap(namedFamily -> namedFamily.getFamilies().stream()).toList();
+
+ return Stream.concat(fontConfig.getFontFamilies().stream(), namedFamilies.stream())
.flatMap(family -> family.getFontList().stream())
- .filter(font -> psName.equals(font.getPostScriptName()))
+ .filter(font -> {
+ Log.e("Debug", "PsName = " + font.getPostScriptName());
+ return psName.equals(font.getPostScriptName());
+ })
// Return the last match, because the latter family takes precedence if two families
// have the same name.
.reduce((first, second) -> second)
@@ -445,10 +458,10 @@
.getAbsolutePath();
}
- private FontConfig.FontFamily findFontFamilyOrThrow(String familyName) {
+ private FontConfig.NamedFamilyList findFontFamilyOrThrow(String familyName) {
FontConfig fontConfig =
SystemUtil.runWithShellPermissionIdentity(mFontManager::getFontConfig);
- return fontConfig.getFontFamilies().stream()
+ return fontConfig.getNamedFamilyLists().stream()
.filter(family -> familyName.equals(family.getName()))
// Return the last match, because the latter family takes precedence if two families
// have the same name.