Snap for 11390602 from 72bc5c0b7ac57a4001c4bd5fd0f0924d9a05c673 to mainline-wifi-release
Change-Id: I0516075fc7e1b74ce275dbd75a405b1c66d7c2c2
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index 6bf7182..2e54d0d 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -47,6 +47,14 @@
<!-- Permission required to access CloudMediaProviders. Declared by us -->
<uses-permission android:name="com.android.providers.media.permission.MANAGE_CLOUD_MEDIA_PROVIDERS" />
+ <!-- Allows an application to have access to OWNER_PACKAGE_NAME field of accessible media files.
+ Applications are still required to have read access to media files.
+ <p>Protection level: normal -->
+ <permission android:name="com.android.providers.media.permission.ACCESS_MEDIA_OWNER_PACKAGE_NAME"
+ android:label="@string/permlab_accessMediaOwnerPackageName"
+ android:description="@string/permdesc_accessMediaOwnerPackageName"
+ android:protectionLevel="normal" />
+
<permission android:name="com.android.providers.media.permission.MANAGE_CLOUD_MEDIA_PROVIDERS"
android:protectionLevel="signature" />
@@ -231,6 +239,9 @@
android:name="com.android.settings.title"
android:resource="@string/picker_settings_system_settings_menu_title"/>
<meta-data
+ android:name="com.android.settings.summary_uri"
+ android:value="content://media/get_cloud_provider_label"/>
+ <meta-data
android:name="com.android.settings.profile"
android:value="primary_profile_only"/>
<!-- ============== END BEGIN SYSTEM SETTINGS MENU ITEM SECTION ============== -->
diff --git a/PREUPLOAD.cfg b/PREUPLOAD.cfg
index 0e2dd2f..1e0d5a0 100644
--- a/PREUPLOAD.cfg
+++ b/PREUPLOAD.cfg
@@ -1,10 +1,16 @@
[Builtin Hooks]
+bpfmt = true
clang_format = true
+ktfmt = true
[Builtin Hooks Options]
clang_format = --commit ${PREUPLOAD_COMMIT} --style file --extensions c,h,cc,cpp
+ktfmt = --kotlinlang-style
[Hook Scripts]
-hidden_api_txt_checksorted_hook = ${REPO_ROOT}/tools/platform-compat/hiddenapi/checksorted_sha.sh ${PREUPLOAD_COMMIT} ${REPO_ROOT}
-
checkstyle_hook = ${REPO_ROOT}/prebuilts/checkstyle/checkstyle.py --sha ${PREUPLOAD_COMMIT}
+hidden_api_txt_checksorted_hook = ${REPO_ROOT}/tools/platform-compat/hiddenapi/checksorted_sha.sh ${PREUPLOAD_COMMIT} ${REPO_ROOT}
+ktlint_hook = ${REPO_ROOT}/prebuilts/ktlint/ktlint.py --no-verify-format -f ${PREUPLOAD_FILES}
+
+[Tool Paths]
+ktfmt = ${REPO_ROOT}/packages/providers/MediaProvider/tools/ktfmt
diff --git a/TEST_MAPPING b/TEST_MAPPING
index 8e8eb1d..deef0dc 100644
--- a/TEST_MAPPING
+++ b/TEST_MAPPING
@@ -114,6 +114,14 @@
"include-annotation": "com.android.providers.media.library.RunOnlyOnPostsubmit"
}
]
+ },
+ {
+ "name": "CtsOsTestCases",
+ "options": [
+ {
+ "include-filter": "android.os.storage.cts.StorageManagerTest"
+ }
+ ]
}
],
"mainline-postsubmit": [
diff --git a/apex/framework/api/current.txt b/apex/framework/api/current.txt
index d760d14..cf13f79 100644
--- a/apex/framework/api/current.txt
+++ b/apex/framework/api/current.txt
@@ -126,6 +126,7 @@
method public static void notifyCloudMediaChangedEvent(@NonNull android.content.ContentResolver, @NonNull String, @NonNull String) throws java.lang.SecurityException;
method @Deprecated @NonNull public static android.net.Uri setIncludePending(@NonNull android.net.Uri);
method @NonNull public static android.net.Uri setRequireOriginal(@NonNull android.net.Uri);
+ field @FlaggedApi("com.android.providers.media.flags.access_media_owner_package_name_permission") public static final String ACCESS_MEDIA_OWNER_PACKAGE_NAME_PERMISSION = "com.android.providers.media.permission.ACCESS_MEDIA_OWNER_PACKAGE_NAME";
field public static final String ACTION_IMAGE_CAPTURE = "android.media.action.IMAGE_CAPTURE";
field public static final String ACTION_IMAGE_CAPTURE_SECURE = "android.media.action.IMAGE_CAPTURE_SECURE";
field public static final String ACTION_PICK_IMAGES = "android.provider.action.PICK_IMAGES";
diff --git a/apex/framework/java/android/provider/AsyncContentProvider.java b/apex/framework/java/android/provider/AsyncContentProvider.java
index 25d5609..e12a0f6 100644
--- a/apex/framework/java/android/provider/AsyncContentProvider.java
+++ b/apex/framework/java/android/provider/AsyncContentProvider.java
@@ -37,7 +37,7 @@
*/
public final class AsyncContentProvider {
- private static final long TIMEOUT_IN_SECONDS = 5L;
+ private static final long TIMEOUT_IN_MINUTES = 3L;
private final IAsyncContentProvider mAsyncContentProvider;
@@ -53,7 +53,7 @@
CompletableFuture<ParcelFileDescriptor> future = new CompletableFuture<>();
RemoteCallback callback = new RemoteCallback(result -> setResult(result, future));
mAsyncContentProvider.openMedia(mediaId, callback);
- return future.get(TIMEOUT_IN_SECONDS, TimeUnit.SECONDS);
+ return future.get(TIMEOUT_IN_MINUTES, TimeUnit.MINUTES);
}
private void setResult(Bundle result, CompletableFuture<ParcelFileDescriptor> future) {
@@ -71,4 +71,4 @@
+ "CloudMediaProvider"));
}
}
-}
\ No newline at end of file
+}
diff --git a/apex/framework/java/android/provider/CloudMediaProvider.java b/apex/framework/java/android/provider/CloudMediaProvider.java
index 665739e..4bddd86 100644
--- a/apex/framework/java/android/provider/CloudMediaProvider.java
+++ b/apex/framework/java/android/provider/CloudMediaProvider.java
@@ -51,6 +51,7 @@
import android.graphics.Point;
import android.media.MediaPlayer;
import android.net.Uri;
+import android.os.Binder;
import android.os.Bundle;
import android.os.CancellationSignal;
import android.os.IBinder;
@@ -124,6 +125,9 @@
private final UriMatcher mMatcher = new UriMatcher(UriMatcher.NO_MATCH);
private volatile int mMediaStoreAuthorityAppId;
+ private String mAuthority;
+
+
private final AsyncContentProviderWrapper mAsyncContentProviderWrapper =
new AsyncContentProviderWrapper();
@@ -138,6 +142,7 @@
}
private void registerAuthority(String authority) {
+ mAuthority = authority;
mMatcher.addURI(authority, URI_PATH_MEDIA, MATCH_MEDIAS);
mMatcher.addURI(authority, URI_PATH_DELETED_MEDIA, MATCH_DELETED_MEDIAS);
mMatcher.addURI(authority, URI_PATH_ALBUM, MATCH_ALBUMS);
@@ -361,15 +366,21 @@
private Bundle callUnchecked(String method, String arg, Bundle extras)
throws FileNotFoundException {
+ Bundle result = new Bundle();
if (METHOD_GET_MEDIA_COLLECTION_INFO.equals(method)) {
- return onGetMediaCollectionInfo(extras);
+ long startTime = System.currentTimeMillis();
+ result = onGetMediaCollectionInfo(extras);
+ CmpApiVerifier.verifyApiResult(new CmpApiResult(
+ CmpApiVerifier.CloudMediaProviderApis.OnGetMediaCollectionInfo, result),
+ System.currentTimeMillis() - startTime, mAuthority);
} else if (METHOD_CREATE_SURFACE_CONTROLLER.equals(method)) {
- return onCreateCloudMediaSurfaceController(extras);
+ result = onCreateCloudMediaSurfaceController(extras);
} else if (METHOD_GET_ASYNC_CONTENT_PROVIDER.equals(method)) {
- return onGetAsyncContentProvider();
+ result = onGetAsyncContentProvider();
} else {
throw new UnsupportedOperationException("Method not supported " + method);
}
+ return result;
}
private Bundle onCreateCloudMediaSurfaceController(@NonNull Bundle extras) {
@@ -428,7 +439,12 @@
@Nullable CancellationSignal signal) throws FileNotFoundException {
String mediaId = uri.getLastPathSegment();
- return onOpenMedia(mediaId, /* extras */ null, signal);
+ long startTime = System.currentTimeMillis();
+ ParcelFileDescriptor result = onOpenMedia(mediaId, /* extras */ null, signal);
+ CmpApiVerifier.verifyApiResult(new CmpApiResult(
+ CmpApiVerifier.CloudMediaProviderApis.OnOpenMedia, result),
+ System.currentTimeMillis() - startTime, mAuthority);
+ return result;
}
/**
@@ -477,7 +493,12 @@
previewSize = new Point(minPreviewLength, minPreviewLength);
}
- return onOpenPreview(mediaId, previewSize, bundle, signal);
+ long startTime = System.currentTimeMillis();
+ AssetFileDescriptor result = onOpenPreview(mediaId, previewSize, bundle, signal);
+ CmpApiVerifier.verifyApiResult(new CmpApiResult(
+ CmpApiVerifier.CloudMediaProviderApis.OnOpenPreview, result, previewSize),
+ System.currentTimeMillis() - startTime, mAuthority);
+ return result;
}
/**
@@ -491,16 +512,34 @@
@Override
public final Cursor query(@NonNull Uri uri, @Nullable String[] projection,
@Nullable Bundle queryArgs, @Nullable CancellationSignal cancellationSignal) {
+ if (queryArgs == null) {
+ queryArgs = new Bundle();
+ }
+ Cursor result;
+ long startTime = System.currentTimeMillis();
switch (mMatcher.match(uri)) {
case MATCH_MEDIAS:
- return onQueryMedia(queryArgs);
+ result = onQueryMedia(queryArgs);
+ CmpApiVerifier.verifyApiResult(new CmpApiResult(
+ CmpApiVerifier.CloudMediaProviderApis.OnQueryMedia, result),
+ System.currentTimeMillis() - startTime, mAuthority);
+ break;
case MATCH_DELETED_MEDIAS:
- return onQueryDeletedMedia(queryArgs);
+ result = onQueryDeletedMedia(queryArgs);
+ CmpApiVerifier.verifyApiResult(new CmpApiResult(
+ CmpApiVerifier.CloudMediaProviderApis.OnQueryDeletedMedia, result),
+ System.currentTimeMillis() - startTime, mAuthority);
+ break;
case MATCH_ALBUMS:
- return onQueryAlbums(queryArgs);
+ result = onQueryAlbums(queryArgs);
+ CmpApiVerifier.verifyApiResult(new CmpApiResult(
+ CmpApiVerifier.CloudMediaProviderApis.OnQueryAlbums, result),
+ System.currentTimeMillis() - startTime, mAuthority);
+ break;
default:
throw new UnsupportedOperationException("Unsupported Uri " + uri);
}
+ return result;
}
/**
diff --git a/apex/framework/java/android/provider/CmpApiResult.java b/apex/framework/java/android/provider/CmpApiResult.java
new file mode 100644
index 0000000..4dc1158
--- /dev/null
+++ b/apex/framework/java/android/provider/CmpApiResult.java
@@ -0,0 +1,80 @@
+/*
+ * Copyright (C) 2024 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.provider;
+
+import android.content.res.AssetFileDescriptor;
+import android.database.Cursor;
+import android.graphics.Point;
+import android.os.Bundle;
+import android.os.ParcelFileDescriptor;
+
+/**
+ * Used to store the returned results from CloudProviderApis.
+ */
+class CmpApiResult {
+ private String mApi;
+ private Cursor mCursor;
+ private Bundle mBundle;
+ private Point mDimensions;
+ private ParcelFileDescriptor mParcelFileDescriptor;
+ private AssetFileDescriptor mAssetFileDescriptor;
+
+ CmpApiResult(String api, Cursor c) {
+ mApi = api;
+ mCursor = c;
+ }
+
+ CmpApiResult(String api, AssetFileDescriptor afd, Point dimensions) {
+ mApi = api;
+ mAssetFileDescriptor = afd;
+ mDimensions = dimensions;
+ }
+
+ CmpApiResult(String api, Bundle bundle) {
+ mApi = api;
+ mBundle = bundle;
+ }
+
+ CmpApiResult(String api, ParcelFileDescriptor pfd) {
+ mApi = api;
+ mParcelFileDescriptor = pfd;
+ }
+
+ String getApi() {
+ return mApi;
+ }
+
+ Cursor getCursor() {
+ return mCursor;
+ }
+
+ Bundle getBundle() {
+ return mBundle;
+ }
+
+ Point getDimensions() {
+ return mDimensions;
+ }
+
+ ParcelFileDescriptor getParcelFileDescriptor() {
+ return mParcelFileDescriptor;
+ }
+
+ AssetFileDescriptor getAssetFileDescriptor() {
+ return mAssetFileDescriptor;
+ }
+}
diff --git a/apex/framework/java/android/provider/CmpApiVerifier.java b/apex/framework/java/android/provider/CmpApiVerifier.java
new file mode 100644
index 0000000..dc948d4
--- /dev/null
+++ b/apex/framework/java/android/provider/CmpApiVerifier.java
@@ -0,0 +1,392 @@
+/*
+ * Copyright (C) 2024 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.provider;
+
+import static android.provider.VerificationLogsHelper.createIsNotNullLog;
+import static android.provider.VerificationLogsHelper.createIsNotValidLog;
+import static android.provider.VerificationLogsHelper.createIsNullLog;
+import static android.provider.VerificationLogsHelper.logVerifications;
+import static android.provider.VerificationLogsHelper.verifyCursorNotNullAndMediaCollectionIdPresent;
+import static android.provider.VerificationLogsHelper.verifyMediaCollectionId;
+import static android.provider.VerificationLogsHelper.verifyProjectionForCursor;
+import static android.provider.VerificationLogsHelper.verifyTotalTimeForExecution;
+
+import android.annotation.StringDef;
+import android.content.Intent;
+import android.content.res.AssetFileDescriptor;
+import android.database.Cursor;
+import android.graphics.BitmapFactory;
+import android.graphics.Point;
+import android.os.Bundle;
+import android.os.ParcelFileDescriptor;
+import android.os.SystemProperties;
+import android.util.Log;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Provides helper methods that help verify that the received results from cloud provider
+ * implementations are staying true to contract by returning non null outputs and setting required
+ * extras/states in the result.
+ *
+ * Note: logs for local provider and not printed.
+ */
+final class CmpApiVerifier {
+ private static final String LOCAL_PROVIDER_AUTHORITY =
+ "com.android.providers.media.photopicker";
+
+ private static boolean isCloudMediaProviderLoggingEnabled() {
+ return (SystemProperties.getInt("ro.debuggable", 0) == 1) && Log.isLoggable(
+ "CloudMediaProvider", Log.VERBOSE);
+ }
+
+ /**
+ * Verifies and logs results received by CloudMediaProvider Apis.
+ *
+ * <p><b>Note:</b> It only logs the errors and does not throw any exceptions.
+ */
+ static void verifyApiResult(CmpApiResult result, long totalTimeTakenForExecution,
+ String authority) {
+ // Do not perform any operation if the authority is of the local provider or when the
+ // logging is not enabled.
+ if (!LOCAL_PROVIDER_AUTHORITY.equals(authority)
+ && isCloudMediaProviderLoggingEnabled()) {
+ try {
+ ArrayList<String> verificationResult = new ArrayList<>();
+ ArrayList<String> errors = new ArrayList<>();
+ verifyTotalTimeForExecution(totalTimeTakenForExecution,
+ CMP_API_TO_THRESHOLD_MAP.get(result.getApi()), errors);
+
+ switch (result.getApi()) {
+ case CloudMediaProviderApis.OnGetMediaCollectionInfo: {
+ verifyOnGetMediaCollectionInfo(result.getBundle(), verificationResult,
+ errors);
+ break;
+ }
+ case CloudMediaProviderApis.OnQueryMedia: {
+ verifyOnQueryMedia(result.getCursor(), verificationResult, errors);
+ break;
+ }
+ case CloudMediaProviderApis.OnQueryDeletedMedia: {
+ verifyOnQueryDeletedMedia(result.getCursor(), verificationResult, errors);
+ break;
+ }
+ case CloudMediaProviderApis.OnQueryAlbums: {
+ verifyOnQueryAlbums(result.getCursor(), verificationResult, errors);
+ break;
+ }
+ case CloudMediaProviderApis.OnOpenPreview: {
+ verifyOnOpenPreview(result.getAssetFileDescriptor(), result.getDimensions(),
+ verificationResult, errors);
+ break;
+ }
+ case CloudMediaProviderApis.OnOpenMedia: {
+ verifyOnOpenMedia(result.getParcelFileDescriptor(), verificationResult,
+ errors);
+ break;
+ }
+ default:
+ throw new UnsupportedOperationException(
+ "The verification for requested API is not supported.");
+ }
+ logVerifications(authority, result.getApi(), totalTimeTakenForExecution,
+ verificationResult, errors);
+ } catch (Exception e) {
+ VerificationLogsHelper.logException(e.getMessage());
+ }
+ }
+ }
+
+ /**
+ * Verifies OnGetMediaCollectionInfo API by performing and logging the following checks:
+ *
+ * <ul>
+ * <li>Received Bundle is not null.</li>
+ * <li>Bundle contains media collection ID:
+ * {@link CloudMediaProviderContract.MediaCollectionInfo#MEDIA_COLLECTION_ID}</li>
+ * <li>Bundle contains last sync generation:
+ * {@link CloudMediaProviderContract.MediaCollectionInfo#LAST_MEDIA_SYNC_GENERATION}</li>
+ * <li>Bundle contains account name:
+ * {@link CloudMediaProviderContract.MediaCollectionInfo#ACCOUNT_NAME}</li>
+ * <li>Bundle contains account configuration intent:
+ * {@link CloudMediaProviderContract.MediaCollectionInfo#ACCOUNT_CONFIGURATION_INTENT}</li>
+ * </ul>
+ */
+ static void verifyOnGetMediaCollectionInfo(
+ Bundle outputBundle, List<String> verificationResult, List<String> errors
+ ) {
+ if (outputBundle != null) {
+ verificationResult.add(createIsNotNullLog("Received bundle"));
+
+ String mediaCollectionId = outputBundle.getString(
+ CloudMediaProviderContract.MediaCollectionInfo.MEDIA_COLLECTION_ID
+ );
+ // verifies media collection id.
+ verifyMediaCollectionId(
+ mediaCollectionId,
+ verificationResult,
+ errors
+ );
+
+ long syncGeneration = outputBundle.getLong(
+ CloudMediaProviderContract.MediaCollectionInfo.LAST_MEDIA_SYNC_GENERATION,
+ -1L
+ );
+
+ // verified last sync generation.
+ if (syncGeneration != -1L) {
+ if (syncGeneration >= 0) {
+ verificationResult.add(
+ CloudMediaProviderContract.MediaCollectionInfo
+ .LAST_MEDIA_SYNC_GENERATION + " : " + syncGeneration
+ );
+ } else {
+ errors.add(
+ CloudMediaProviderContract.MediaCollectionInfo
+ .LAST_MEDIA_SYNC_GENERATION + " is < 0"
+ );
+ }
+ } else {
+ errors.add(
+ createIsNotValidLog(
+ CloudMediaProviderContract.MediaCollectionInfo
+ .LAST_MEDIA_SYNC_GENERATION
+ )
+ );
+ }
+
+ String accountName = outputBundle.getString(
+ CloudMediaProviderContract.MediaCollectionInfo.ACCOUNT_NAME
+ );
+
+ // verifies account name.
+ if (accountName != null) {
+ if (!accountName.isEmpty()) {
+ // In future if the cloud media provider is extended to have multiple
+ // accounts then logging account name itself might be a useful
+ // information to log but for now only logging its presence.
+ verificationResult.add(
+ CloudMediaProviderContract.MediaCollectionInfo.ACCOUNT_NAME
+ + " is present "
+ );
+ } else {
+ errors.add(
+ CloudMediaProviderContract.MediaCollectionInfo.ACCOUNT_NAME
+ + " is empty"
+ );
+ }
+ } else {
+ errors.add(createIsNullLog(
+ CloudMediaProviderContract.MediaCollectionInfo.ACCOUNT_NAME
+ )
+ );
+ }
+
+ Intent intent = outputBundle.getParcelable(
+ CloudMediaProviderContract.MediaCollectionInfo.ACCOUNT_CONFIGURATION_INTENT
+ );
+ // verified the presence of account configuration intent.
+ if (intent != null) {
+ verificationResult.add(
+ CloudMediaProviderContract.MediaCollectionInfo
+ .ACCOUNT_CONFIGURATION_INTENT
+ + " is present."
+ );
+ } else {
+ errors.add(createIsNullLog(
+ CloudMediaProviderContract.MediaCollectionInfo
+ .ACCOUNT_CONFIGURATION_INTENT
+ )
+ );
+ }
+
+ } else {
+ errors.add(createIsNullLog("Received output bundle"));
+ }
+ }
+
+ /**
+ * Verifies OnQueryMedia API by performing and logging the following checks:
+ *
+ * <ul>
+ * <li>Received Cursor is not null.</li>
+ * <li>Cursor contains non empty media collection ID:
+ * {@link CloudMediaProviderContract#EXTRA_MEDIA_COLLECTION_ID}</li>
+ * <li>Projection for cursor is as expected:
+ * {@link CloudMediaProviderContract.MediaColumns#ALL_PROJECTION}</li>
+ * <li>Logs count of rows in the cursor, if cursor is non null.</li>
+ * </ul>
+ */
+ static void verifyOnQueryMedia(
+ Cursor c, List<String> verificationResult, List<String> errors
+ ) {
+ if (c != null) {
+ verifyCursorNotNullAndMediaCollectionIdPresent(
+ c,
+ verificationResult,
+ errors
+ );
+ // verify that all columns are present per CloudMediaProviderContract.AlbumColumns
+ verifyProjectionForCursor(
+ c,
+ Arrays.asList(CloudMediaProviderContract.MediaColumns.ALL_PROJECTION),
+ errors
+ );
+ } else {
+ errors.add(createIsNullLog("Received cursor"));
+ }
+ }
+
+ /**
+ * Verifies OnQueryDeletedMedia API by performing and logging the following checks:
+ *
+ * <ul>
+ * <li>Received Cursor is not null.</li>
+ * <li>Cursor contains non empty media collection ID:
+ * {@link CloudMediaProviderContract#EXTRA_MEDIA_COLLECTION_ID}</li>
+ * <li>Logs count of rows in the cursor, if cursor is non null.</li>
+ * </ul>
+ */
+ static void verifyOnQueryDeletedMedia(
+ Cursor c, List<String> verificationResult, List<String> errors
+ ) {
+ verifyCursorNotNullAndMediaCollectionIdPresent(c, verificationResult, errors);
+ }
+
+ /**
+ * Verifies OnQueryAlbums API by performing and logging the following checks:
+ *
+ * <ul>
+ * <li>Received Cursor is not null.</li>
+ * <li>Cursor contains non empty media collection ID:
+ * {@link CloudMediaProviderContract#EXTRA_MEDIA_COLLECTION_ID}</li>
+ * <li>Projection for cursor is as expected:
+ * {@link CloudMediaProviderContract.AlbumColumns#ALL_PROJECTION}</li>
+ * <li>Logs count of rows in the cursor and the album names, if cursor is non null.</li>
+ * </ul>
+ */
+ static void verifyOnQueryAlbums(
+ Cursor c, List<String> verificationResult, List<String> errors
+ ) {
+ if (c != null) {
+ verifyCursorNotNullAndMediaCollectionIdPresent(c, verificationResult, errors);
+
+ // verify that all columns are present per CloudMediaProviderContract.AlbumColumns
+ verifyProjectionForCursor(
+ c,
+ Arrays.asList(CloudMediaProviderContract.AlbumColumns.ALL_PROJECTION),
+ errors
+ );
+ if (c.getCount() > 0) {
+ // Only log album data if projection and other checks have returned positive
+ // results.
+ StringBuilder strBuilder = new StringBuilder("Albums present and their count: ");
+ int columnIndexForId = c.getColumnIndex(CloudMediaProviderContract.AlbumColumns.ID);
+ int columnIndexForItemCount = c.getColumnIndex(
+ CloudMediaProviderContract.AlbumColumns.MEDIA_COUNT);
+ c.moveToPosition(-1);
+ while (c.moveToNext()) {
+ strBuilder.append("\n\t\t\t" + c.getString(columnIndexForId) + ", " + c.getLong(
+ columnIndexForItemCount));
+ }
+ c.moveToPosition(-1);
+ verificationResult.add(strBuilder.toString());
+ }
+ }
+ }
+
+
+ /**
+ * Verifies OnOpenPreview API by performing and logging the following checks:
+ *
+ * <ul>
+ * <li>Received AssetFileDescriptor is not null.</li>
+ * <li>Logs size of the thumbnail.</li>
+ * </ul>
+ */
+ static void verifyOnOpenPreview(
+ AssetFileDescriptor assetFileDescriptor,
+ Point expectedSize, List<String> verificationResult, List<String> errors
+ ) {
+ if (assetFileDescriptor == null) {
+ errors.add(createIsNullLog("Received AssetFileDescriptor"));
+ } else {
+ verificationResult.add(createIsNotNullLog("Received AssetFileDescriptor"));
+ BitmapFactory.Options options = new BitmapFactory.Options();
+ options.inJustDecodeBounds = true; // Only decode the bounds
+ BitmapFactory.decodeFileDescriptor(assetFileDescriptor.getFileDescriptor(), null,
+ options);
+
+ int width = options.outWidth;
+ int height = options.outHeight;
+
+ verificationResult.add("Dimensions of file received: "
+ + "Width: " + width + ", Height: " + height + ", expected: " + expectedSize.x
+ + ", " + expectedSize.y);
+ }
+ }
+
+ /**
+ * Verifies OnOpenMedia API by performing and logging the following checks:
+ *
+ * <ul>
+ * <li>Received ParcelFileDescriptor is not null.</li>
+ * </ul>
+ */
+ static void verifyOnOpenMedia(
+ ParcelFileDescriptor fd,
+ List<String> verificationResult, List<String> errors
+ ) {
+ if (fd == null) {
+ errors.add(createIsNullLog("Received FileDescriptor"));
+ } else {
+ verificationResult.add(createIsNotNullLog("Received FileDescriptor"));
+ }
+ }
+
+ @StringDef({
+ CloudMediaProviderApis.OnGetMediaCollectionInfo,
+ CloudMediaProviderApis.OnQueryMedia,
+ CloudMediaProviderApis.OnQueryDeletedMedia,
+ CloudMediaProviderApis.OnQueryAlbums,
+ CloudMediaProviderApis.OnOpenPreview,
+ CloudMediaProviderApis.OnOpenMedia
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ @interface CloudMediaProviderApis {
+ String OnGetMediaCollectionInfo = "onGetMediaCollectionInfo";
+ String OnQueryMedia = "onQueryMedia";
+ String OnQueryDeletedMedia = "onQueryDeletedMedia";
+ String OnQueryAlbums = "onQueryAlbums";
+ String OnOpenPreview = "onOpenPreview";
+ String OnOpenMedia = "onOpenMedia";
+ }
+
+ private static final Map<String, Long> CMP_API_TO_THRESHOLD_MAP = Map.of(
+ CloudMediaProviderApis.OnGetMediaCollectionInfo, 200L,
+ CloudMediaProviderApis.OnQueryMedia, 500L,
+ CloudMediaProviderApis.OnQueryDeletedMedia, 500L,
+ CloudMediaProviderApis.OnQueryAlbums, 500L,
+ CloudMediaProviderApis.OnOpenPreview, 1000L,
+ CloudMediaProviderApis.OnOpenMedia, 1000L
+ );
+}
diff --git a/apex/framework/java/android/provider/MediaStore.java b/apex/framework/java/android/provider/MediaStore.java
index d232fd3..614c186 100644
--- a/apex/framework/java/android/provider/MediaStore.java
+++ b/apex/framework/java/android/provider/MediaStore.java
@@ -261,6 +261,8 @@
/** {@hide} */
public static final String GET_CLOUD_PROVIDER_RESULT = "get_cloud_provider_result";
/** {@hide} */
+ public static final String GET_CLOUD_PROVIDER_LABEL_CALL = "get_cloud_provider_label";
+ /** {@hide} */
public static final String SET_CLOUD_PROVIDER_RESULT = "set_cloud_provider_result";
/** {@hide} */
public static final String SET_CLOUD_PROVIDER_CALL = "set_cloud_provider";
@@ -1088,6 +1090,14 @@
@Match
public static final String QUERY_ARG_MATCH_FAVORITE = "android:query-arg-match-favorite";
+ /**
+ * Permission that grants access to {@link MediaColumns#OWNER_PACKAGE_NAME}
+ * of every accessible media file.
+ */
+ @FlaggedApi("com.android.providers.media.flags.access_media_owner_package_name_permission")
+ public static final String ACCESS_MEDIA_OWNER_PACKAGE_NAME_PERMISSION =
+ "com.android.providers.media.permission.ACCESS_MEDIA_OWNER_PACKAGE_NAME";
+
/** @hide */
@IntDef(flag = true, prefix = { "MATCH_" }, value = {
MATCH_DEFAULT,
diff --git a/apex/framework/java/android/provider/VerificationLogsHelper.java b/apex/framework/java/android/provider/VerificationLogsHelper.java
new file mode 100644
index 0000000..4e6f59d
--- /dev/null
+++ b/apex/framework/java/android/provider/VerificationLogsHelper.java
@@ -0,0 +1,137 @@
+/*
+ * Copyright (C) 2024 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.provider;
+
+import android.database.Cursor;
+import android.util.Log;
+
+import java.util.List;
+
+/**
+ * Helper class to create log strings.
+ */
+final class VerificationLogsHelper {
+ public static final String TAG = "CMP_verifications_";
+
+ /**
+ * Verifies that all expected columns are present in the returned cursor.
+ */
+ static void verifyProjectionForCursor(
+ Cursor cursor,
+ List<String> expectedColumns,
+ List<String> errors
+ ) {
+ // TODO: ignore non compulsory columns?
+ for (String column : expectedColumns) {
+ if (cursor.getColumnIndex(column) == -1) {
+ errors.add(createColumnNotPresentLog(column));
+ }
+ }
+ }
+
+ /**
+ * Verifies and logs whether MediaCollectionId is null or empty.
+ */
+ static void verifyMediaCollectionId(
+ String mediaCollectionId,
+ List<String> verificationResult,
+ List<String> errors
+ ) {
+ if (mediaCollectionId != null && !mediaCollectionId.isEmpty()) {
+ verificationResult.add(
+ CloudMediaProviderContract.MediaCollectionInfo.MEDIA_COLLECTION_ID
+ + " : " + mediaCollectionId
+ );
+ } else {
+ errors.add(CloudMediaProviderContract.MediaCollectionInfo.MEDIA_COLLECTION_ID
+ + (mediaCollectionId == null ? " is null" : " is empty"));
+ }
+ }
+
+ /**
+ * Verifies and logs if the cursor is null or not and the mediaCollectionId inside it.
+ */
+ static void verifyCursorNotNullAndMediaCollectionIdPresent(
+ Cursor c,
+ List<String> verificationResult,
+ List<String> errors
+ ) {
+ if (c != null) {
+ verificationResult.add(createIsNotNullLog("Received cursor"));
+ verificationResult.add(String.format("Number of items in cursor: %s", c.getCount()));
+ verifyMediaCollectionId(
+ c.getExtras().getString(CloudMediaProviderContract.EXTRA_MEDIA_COLLECTION_ID),
+ verificationResult,
+ errors
+ );
+ } else {
+ errors.add(createIsNullLog("Received cursor"));
+ }
+ }
+
+ /**
+ * Helps log an error for when execution time for an API exceeds the designated threshold.
+ */
+ static void verifyTotalTimeForExecution(long totalTimeTakenForExecution, long threshold,
+ List<String> errors) {
+ if (threshold < totalTimeTakenForExecution) {
+ errors.add("Total time for execution exceeded threshold. threshold = " + threshold
+ + "ms, totalTimeForExecution = " + totalTimeTakenForExecution + "ms");
+ }
+ }
+
+ static String createIsNotNullLog(String value) {
+ return String.format("%s is not null.", value);
+ }
+
+ static String createIsNullLog(String value) {
+ return String.format("%s is null.", value);
+ }
+
+ static String createIsNotValidLog(String value) {
+ return String.format("%s is not valid.", value);
+ }
+
+ static String createColumnNotPresentLog(String value) {
+ return String.format("%s column is not present in the returned cursor.", value);
+ }
+
+ static void logVerifications(String authority, String apiName,
+ long totalTimeTakenForExecution,
+ List<String> verifications, List<String> errors) {
+ StringBuilder strb = new StringBuilder("Verifications for : " + apiName + "\n");
+ strb.append("\tTotal time for execution: ").append(totalTimeTakenForExecution).append(
+ "ms \n");
+ if (!verifications.isEmpty()) {
+ strb.append("\tVerifications:\n");
+ for (String verification : verifications) {
+ strb.append("\t\t").append(verification).append("\n");
+ }
+ }
+ if (!errors.isEmpty()) {
+ strb.append("\tErrors:\n");
+ for (String error : errors) {
+ strb.append("\t\t").append(error).append("\n");
+ }
+ }
+ Log.d(TAG + authority, strb.toString());
+ }
+
+ static void logException(String exceptionMessage) {
+ Log.d(TAG + "Exception", exceptionMessage);
+ }
+}
diff --git a/jni/FuseDaemon.cpp b/jni/FuseDaemon.cpp
index f261ae2..8e04a53 100644
--- a/jni/FuseDaemon.cpp
+++ b/jni/FuseDaemon.cpp
@@ -2769,6 +2769,33 @@
return resultMap;
}
+void FuseDaemon::RemoveLevelDbConnections(const std::string& volume_name) {
+ std::map<std::string, std::string> resultMap;
+ if (!CheckLevelDbConnection(volume_name)) {
+ LOG(ERROR) << "Skipping connection removal, as level db connection is missing.";
+ return;
+ }
+
+ RemoveConnectionForInstance(volume_name);
+ if (android::base::EqualsIgnoreCase(volume_name, VOLUME_EXTERNAL_PRIMARY)) {
+ RemoveConnectionForInstance(VOLUME_INTERNAL);
+ RemoveConnectionForInstance(OWNERSHIP_RELATION);
+ }
+}
+
+void FuseDaemon::RemoveConnectionForInstance(const std::string& instance_name) {
+ if (!CheckLevelDbConnection(instance_name)) {
+ LOG(ERROR) << "Skipping connection removal, as level db connection is missing.";
+ return;
+ }
+
+ fuse->level_db_mutex.lock();
+ leveldb::DB* db = fuse->level_db_connection_map[instance_name];
+ delete db;
+ fuse->level_db_connection_map.erase(instance_name);
+ fuse->level_db_mutex.unlock();
+}
+
bool FuseDaemon::CheckLevelDbConnection(const std::string& instance_name) {
if (fuse->level_db_connection_map.find(instance_name) == fuse->level_db_connection_map.end()) {
LOG(ERROR) << "Leveldb setup is missing for: " << instance_name;
diff --git a/jni/FuseDaemon.h b/jni/FuseDaemon.h
index a9eaf22..e5776c1 100644
--- a/jni/FuseDaemon.h
+++ b/jni/FuseDaemon.h
@@ -138,6 +138,16 @@
*/
bool CheckLevelDbConnection(const std::string& instance_name);
+ /**
+ * Removes level db connection for detached volume.
+ */
+ void RemoveLevelDbConnections(const std::string& volume_name);
+
+ /**
+ * Removes level db connection for detached volume.
+ */
+ void RemoveConnectionForInstance(const std::string& instance_name);
+
private:
FuseDaemon(const FuseDaemon&) = delete;
void operator=(const FuseDaemon&) = delete;
diff --git a/jni/com_android_providers_media_FuseDaemon.cpp b/jni/com_android_providers_media_FuseDaemon.cpp
index 2b78645..a762f64 100644
--- a/jni/com_android_providers_media_FuseDaemon.cpp
+++ b/jni/com_android_providers_media_FuseDaemon.cpp
@@ -320,6 +320,14 @@
utf_chars_owner_pkg_identifier.c_str());
}
+void com_android_providers_media_FuseDaemon_remove_leveldb_connections(JNIEnv* env, jobject self,
+ jlong java_daemon,
+ jstring volume_name) {
+ fuse::FuseDaemon* const daemon = reinterpret_cast<fuse::FuseDaemon*>(java_daemon);
+ ScopedUtfChars utf_chars_volume_name(env, volume_name);
+ daemon->RemoveLevelDbConnections(utf_chars_volume_name.c_str());
+}
+
const JNINativeMethod methods[] = {
{"native_new", "(Lcom/android/providers/media/MediaProvider;)J",
reinterpret_cast<void*>(com_android_providers_media_FuseDaemon_new)},
@@ -362,7 +370,10 @@
{"native_read_owner_relations", "(J)Ljava/util/HashMap;",
reinterpret_cast<void*>(com_android_providers_media_FuseDaemon_read_owner_relations)},
{"native_remove_owner_id_relation", "(JLjava/lang/String;Ljava/lang/String;)V",
- reinterpret_cast<void*>(com_android_providers_media_FuseDaemon_remove_owner_id_relation)}};
+ reinterpret_cast<void*>(com_android_providers_media_FuseDaemon_remove_owner_id_relation)},
+ {"native_remove_leveldb_connections", "(JLjava/lang/String;)V",
+ reinterpret_cast<void*>(
+ com_android_providers_media_FuseDaemon_remove_leveldb_connections)}};
} // namespace
void register_android_providers_media_FuseDaemon(JavaVM* vm, JNIEnv* env) {
diff --git a/jni/node-inl.h b/jni/node-inl.h
index 15844e3..ba3d926 100644
--- a/jni/node-inl.h
+++ b/jni/node-inl.h
@@ -322,12 +322,20 @@
}
void DestroyHandle(handle* h) {
- std::lock_guard<std::recursive_mutex> guard(*lock_);
+ // A deadlock occurs between FuseDaemon lock and inode lock in the photo picker scenario.
+ // In order to address this deadlock, we release the FuseDaemon lock before closing the
+ // backing file.
+ std::unique_ptr<handle> temp;
+
+ lock_->lock();
auto comp = [h](const std::unique_ptr<handle>& ptr) { return ptr.get() == h; };
auto it = std::find_if(handles_.begin(), handles_.end(), comp);
CHECK(it != handles_.end());
+ temp = std::move(*it);
handles_.erase(it);
+
+ lock_->unlock();
}
bool HasCachedHandle() const {
diff --git a/photopicker/Android.bp b/photopicker/Android.bp
new file mode 100644
index 0000000..df450d2
--- /dev/null
+++ b/photopicker/Android.bp
@@ -0,0 +1,53 @@
+package {
+ // See: http://go/android-license-faq
+ default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+android_library {
+ name: "PhotopickerLib",
+ manifest: "AndroidManifest.xml",
+ srcs: ["src/**/*.kt"],
+ resource_dirs: ["res"],
+ sdk_version: "module_current",
+ min_sdk_version: "30",
+ libs: [
+ "framework-connectivity.stubs.module_lib",
+ ],
+ static_libs: [
+ "androidx.activity_activity-compose",
+ "androidx.compose.foundation_foundation",
+ "androidx.compose.material3_material3",
+ "androidx.compose.runtime_runtime",
+ "androidx.compose.ui_ui",
+ "androidx.core_core-ktx",
+ "androidx.lifecycle_lifecycle-runtime-compose",
+ "androidx.lifecycle_lifecycle-runtime-ktx",
+ "hilt_android",
+ "kotlin-stdlib",
+ "kotlinx-coroutines-android",
+ "kotlinx_coroutines",
+ ],
+ apex_available: [
+ "//apex_available:platform",
+ "com.android.mediaprovider",
+ ],
+}
+
+android_app {
+ name: "Photopicker",
+ manifest: "AndroidManifest.xml",
+ static_libs: [
+ "PhotopickerLib",
+ ],
+ plugins: [],
+ kotlincflags: ["-Xjvm-default=all"],
+ certificate: "media",
+ privileged: true,
+ sdk_version: "module_current",
+ min_sdk_version: "30",
+ apex_available: [
+ "//apex_available:platform",
+ "com.android.mediaprovider",
+ ],
+
+}
diff --git a/photopicker/AndroidManifest.xml b/photopicker/AndroidManifest.xml
new file mode 100644
index 0000000..1ee8842
--- /dev/null
+++ b/photopicker/AndroidManifest.xml
@@ -0,0 +1,74 @@
+<!--
+ Copyright 2024 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:tools="http://schemas.android.com/tools"
+ package="com.android.photopicker">
+
+ <!--
+ This permission identifies Photopicker to MediaProvider and allows access
+ to private system APIs.
+
+ Declared by MediaProvider and requires the 'media' certificate to obtain.
+ -->
+ <uses-permission
+ android:name="com.android.providers.media.permission.MANAGE_CLOUD_MEDIA_PROVIDERS"/>
+
+ <!-- Required to inspect network capabilities through ConnectivityManager -->
+ <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
+
+ <application
+ android:name="com.android.photopicker.PhotopickerApplication"
+ android:label="@string/photopicker_application_label"
+ android:allowBackup="false"
+ android:supportsRtl="true">
+
+ <activity
+ android:name="com.android.photopicker.MainActivity"
+ android:exported="true"
+ android:theme="@style/Theme.Photopicker"
+ android:label="@string/photopicker_application_label"
+ android:excludeFromRecents="true">
+
+ <intent-filter android:priority="105" >
+ <action android:name="android.provider.action.PICK_IMAGES"/>
+ <category android:name="android.intent.category.DEFAULT" />
+ <data android:mimeType="image/*" />
+ <data android:mimeType="video/*" />
+ </intent-filter>
+ <intent-filter android:priority="105" >
+ <action android:name="android.provider.action.PICK_IMAGES"/>
+ <category android:name="android.intent.category.DEFAULT"/>
+ </intent-filter>
+ </activity>
+
+ <activity-alias
+ android:name="com.android.photopicker.PhotopickerGetContentActivity"
+ android:targetActivity="com.android.photopicker.MainActivity"
+ android:exported="true"
+ android:excludeFromRecents="true"
+ android:enabled="true">
+ <intent-filter android:priority="105" >
+ <action android:name="android.intent.action.GET_CONTENT"/>
+ <category android:name="android.intent.category.OPENABLE"/>
+ <category android:name="android.intent.category.DEFAULT"/>
+ <data android:mimeType="image/*"/>
+ <data android:mimeType="video/*"/>
+ </intent-filter>
+ </activity-alias>
+
+ </application>
+</manifest>
diff --git a/photopicker/README b/photopicker/README
new file mode 100644
index 0000000..123c9c6
--- /dev/null
+++ b/photopicker/README
@@ -0,0 +1,47 @@
+######################################
+# Android Photopicker README
+######################################
+
+Note: This photopicker app is currently under development, and is not
+(currently) being shipped with mediaprovider. You might be looking for:
+
+/packages/providers/MediaProvider/photopicker
+
+######################################
+# To install for development / testing:
+######################################
+
+Consider using photopicker_utils.sh for deploying/incremental installs/removing.
+
+Build a mediaprovider APEX which includes Photopicker. The initial deployment
+needs to be from the APEX to ensure Photopicker receives its certificate specific
+permissions.
+
+Incremental builds can be done by making the Photopicker target and directly
+installing the resulting APK.
+
+######################################
+# Troubleshooting
+######################################
+
+Launching ACTION_PICK_IMAGES or ACTION_GET_CONTENT should bring you into the new
+PhotopickerActivity. If not, try debugging the intents to see if the activity
+is getting picked up by Android:
+
+adb shell pm query-activities -a "android.intent.action.GET_CONTENT" -t "image/*"
+
+This should give a print out of all activities (and their respective priorities)
+that can handle this intent and com.android.photopicker.MainActivity should be
+in the list. If not, try the installation steps above again. (Be sure to reboot)
+
+
+######################################
+# Testing
+######################################
+To run the tests:
+
+atest PhotopickerTests
+
+Note: PhotopickerTests bundles the application code with the tests, so the
+app does not need to be installed first for the test suite to be run. The test
+suite will bring along all the code it needs.
diff --git a/photopicker/TEST_MAPPING b/photopicker/TEST_MAPPING
new file mode 100644
index 0000000..c049209
--- /dev/null
+++ b/photopicker/TEST_MAPPING
@@ -0,0 +1,8 @@
+{
+ "presubmit": [
+ {
+ "name": "PhotopickerTests",
+ "options": []
+ }
+ ]
+}
diff --git a/photopicker/res/values-ca/core_strings.xml b/photopicker/res/values-ca/core_strings.xml
new file mode 100644
index 0000000..1947a1e
--- /dev/null
+++ b/photopicker/res/values-ca/core_strings.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Copyright 2024 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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="photopicker_application_label" msgid="3482718304682270084">"Selector de mitjans"</string>
+</resources>
diff --git a/photopicker/res/values-en-rCA/core_strings.xml b/photopicker/res/values-en-rCA/core_strings.xml
new file mode 100644
index 0000000..70a5482
--- /dev/null
+++ b/photopicker/res/values-en-rCA/core_strings.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Copyright 2024 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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="photopicker_application_label" msgid="3482718304682270084">"Media Picker"</string>
+</resources>
diff --git a/photopicker/res/values-en-rXC/core_strings.xml b/photopicker/res/values-en-rXC/core_strings.xml
new file mode 100644
index 0000000..2a55878
--- /dev/null
+++ b/photopicker/res/values-en-rXC/core_strings.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Copyright 2024 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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="photopicker_application_label" msgid="3482718304682270084">"Media Picker"</string>
+</resources>
diff --git a/photopicker/res/values-fr/core_strings.xml b/photopicker/res/values-fr/core_strings.xml
new file mode 100644
index 0000000..0ba8ba1
--- /dev/null
+++ b/photopicker/res/values-fr/core_strings.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Copyright 2024 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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="photopicker_application_label" msgid="3482718304682270084">"Sélecteur de fichiers multimédias"</string>
+</resources>
diff --git a/photopicker/res/values-ja/core_strings.xml b/photopicker/res/values-ja/core_strings.xml
new file mode 100644
index 0000000..0e64c07
--- /dev/null
+++ b/photopicker/res/values-ja/core_strings.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Copyright 2024 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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="photopicker_application_label" msgid="3482718304682270084">"メディア選択ツール"</string>
+</resources>
diff --git a/photopicker/res/values-lo/core_strings.xml b/photopicker/res/values-lo/core_strings.xml
new file mode 100644
index 0000000..6a8c44a
--- /dev/null
+++ b/photopicker/res/values-lo/core_strings.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Copyright 2024 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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="photopicker_application_label" msgid="3482718304682270084">"ຕົວເລືອກສື່"</string>
+</resources>
diff --git a/photopicker/res/values-ms/core_strings.xml b/photopicker/res/values-ms/core_strings.xml
new file mode 100644
index 0000000..3024d37
--- /dev/null
+++ b/photopicker/res/values-ms/core_strings.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Copyright 2024 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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="photopicker_application_label" msgid="3482718304682270084">"Pemilih Media"</string>
+</resources>
diff --git a/photopicker/res/values-tl/core_strings.xml b/photopicker/res/values-tl/core_strings.xml
new file mode 100644
index 0000000..f6b09d5
--- /dev/null
+++ b/photopicker/res/values-tl/core_strings.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Copyright 2024 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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="photopicker_application_label" msgid="3482718304682270084">"Tagapili ng Media"</string>
+</resources>
diff --git a/photopicker/res/values/core_strings.xml b/photopicker/res/values/core_strings.xml
new file mode 100644
index 0000000..632d4e0
--- /dev/null
+++ b/photopicker/res/values/core_strings.xml
@@ -0,0 +1,22 @@
+<!--
+ Copyright 2024 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.
+-->
+
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+
+ <!-- Label to show to user for this package and for Photo picker. -->
+ <string name="photopicker_application_label" translation_description="Label for the application that handles picking media files to share with other apps. [CHAR_LIMIT=NONE]">Media Picker</string>
+
+</resources>
diff --git a/photopicker/res/values/themes.xml b/photopicker/res/values/themes.xml
new file mode 100644
index 0000000..dd73d40
--- /dev/null
+++ b/photopicker/res/values/themes.xml
@@ -0,0 +1,24 @@
+<!--
+ Copyright 2024 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.
+ -->
+<resources>
+ <style name="Theme.Photopicker" parent="@android:style/Theme.DeviceDefault.DayNight">
+ <item name="android:backgroundDimEnabled">true</item>
+ <item name="android:statusBarColor">@android:color/transparent</item>
+ <item name="android:windowBackground">@android:color/transparent</item>
+ <item name="android:windowIsTranslucent">true</item>
+ <item name="android:windowNoTitle">true</item>
+ </style>
+</resources>
diff --git a/photopicker/src/com/android/photopicker/MainActivity.kt b/photopicker/src/com/android/photopicker/MainActivity.kt
new file mode 100644
index 0000000..179cba1
--- /dev/null
+++ b/photopicker/src/com/android/photopicker/MainActivity.kt
@@ -0,0 +1,42 @@
+/*
+ * Copyright 2024 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.photopicker
+
+import android.os.Bundle
+import androidx.activity.ComponentActivity
+import androidx.activity.compose.setContent
+import com.android.photopicker.core.PhotopickerApp
+import dagger.hilt.android.AndroidEntryPoint
+
+/**
+ * This is the main entrypoint into the Android Photopicker.
+ *
+ * This class is responsible for bootstrapping the launched activity, session related dependencies,
+ * and providing the compose ui entrypoint in [[PhotopickerApp]] with everything it needs.
+ */
+@AndroidEntryPoint(ComponentActivity::class)
+class MainActivity : Hilt_MainActivity() {
+ companion object {
+ val TAG: String = "Photopicker"
+ }
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+
+ setContent { PhotopickerApp() }
+ }
+}
diff --git a/photopicker/src/com/android/photopicker/PhotopickerApplication.kt b/photopicker/src/com/android/photopicker/PhotopickerApplication.kt
new file mode 100644
index 0000000..3a9d32d
--- /dev/null
+++ b/photopicker/src/com/android/photopicker/PhotopickerApplication.kt
@@ -0,0 +1,27 @@
+/*
+ * Copyright 2024 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.photopicker
+
+import android.app.Application
+import dagger.hilt.android.HiltAndroidApp
+
+@HiltAndroidApp(Application::class)
+class PhotopickerApplication : Hilt_PhotopickerApplication() {
+ override fun onCreate() {
+ super.onCreate()
+ }
+}
diff --git a/photopicker/src/com/android/photopicker/core/PhotopickerApp.kt b/photopicker/src/com/android/photopicker/core/PhotopickerApp.kt
new file mode 100644
index 0000000..22ef3e0
--- /dev/null
+++ b/photopicker/src/com/android/photopicker/core/PhotopickerApp.kt
@@ -0,0 +1,33 @@
+/*
+ * Copyright 2024 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.photopicker.core
+
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.material3.Surface
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+
+/**
+ * This is the top of the Compose UI node tree. This is called from the MainActivity and is the
+ * top-most [@Composable] in the application. This should not be called except inside an Activity's
+ * [setContent] block.
+ */
+@Composable
+fun PhotopickerApp() {
+ Surface(modifier = Modifier.fillMaxSize()) { Text("Hello World from Photopicker!") }
+}
diff --git a/photopicker/src/com/android/photopicker/core/network/NetworkMonitor.kt b/photopicker/src/com/android/photopicker/core/network/NetworkMonitor.kt
new file mode 100644
index 0000000..db47c3e
--- /dev/null
+++ b/photopicker/src/com/android/photopicker/core/network/NetworkMonitor.kt
@@ -0,0 +1,103 @@
+/*
+ * Copyright 2024 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.photopicker.core.network
+
+import android.content.Context
+import android.net.ConnectivityManager
+import android.net.Network
+import android.net.NetworkCapabilities
+import android.net.NetworkRequest
+import android.util.Log
+import com.android.photopicker.extensions.requireSystemService
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.channels.onFailure
+import kotlinx.coroutines.flow.SharedFlow
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.callbackFlow
+import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.shareIn
+
+/**
+ * Provides a long-living [kotlinx.coroutines.flow.SharedFlow] that represents the device's current
+ * network status given the defined required capabilities.
+ *
+ * This is provided in the Activity and will be lazily initialized to prevent it from being created
+ * before it is needed, but will live as a singleton for the life of the activity once initialized.
+ *
+ * Will emit a value immediately of the current network status, and then re-emit on every network
+ * condition change. Will replay the most recent value for new subscribers. Will stop emitting when
+ * all subscribers have unsubscribed.
+ *
+ * @property context This expects to receive the ActivityContext for the current Photopicker
+ * session.
+ * @property scope The [CoroutineScope] that the [NetworkStatus] flow will share in.
+ */
+class NetworkMonitor(context: Context, private val scope: CoroutineScope) {
+ companion object {
+ val TAG = "PhotopickerNetworkMonitor"
+ }
+ private val connectivityManager: ConnectivityManager = context.requireSystemService()
+
+ val networkStatus: SharedFlow<NetworkStatus> =
+ callbackFlow<NetworkStatus> {
+ val networkStatusCallback =
+ object : ConnectivityManager.NetworkCallback() {
+ override fun onUnavailable() {
+ trySend(NetworkStatus.Unavailable).onFailure {
+ Log.e(TAG, """Failed to notify downstream onUnavailable.""")
+ }
+ }
+
+ override fun onAvailable(network: Network) {
+ trySend(NetworkStatus.Available).onFailure {
+ Log.e(TAG, """Failed to notify downstream onAvailable.""")
+ }
+ }
+
+ override fun onLost(network: Network) {
+ trySend(NetworkStatus.Unavailable).onFailure {
+ Log.e(TAG, """Failed to notify downstream onLost.""")
+ }
+ }
+ }
+
+ val request =
+ NetworkRequest.Builder()
+ .addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
+ .build()
+
+ val isConnected =
+ connectivityManager
+ .getNetworkCapabilities(
+ connectivityManager.activeNetwork,
+ )
+ ?.hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
+ ?: false
+
+ trySend(if (isConnected) NetworkStatus.Available else NetworkStatus.Unavailable)
+
+ connectivityManager.registerNetworkCallback(request, networkStatusCallback)
+
+ awaitClose {
+ Log.d(TAG, """NetworkCallback was closed, unregistering.""")
+ connectivityManager.unregisterNetworkCallback(networkStatusCallback)
+ }
+ }
+ .distinctUntilChanged()
+ .shareIn(scope, SharingStarted.WhileSubscribed(), replay = 1)
+}
diff --git a/photopicker/src/com/android/photopicker/core/network/NetworkStatus.kt b/photopicker/src/com/android/photopicker/core/network/NetworkStatus.kt
new file mode 100644
index 0000000..acf3c95
--- /dev/null
+++ b/photopicker/src/com/android/photopicker/core/network/NetworkStatus.kt
@@ -0,0 +1,24 @@
+/*
+ * Copyright 2024 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.photopicker.core.network
+
+/** Simplified representation of the network environment from the ConnectivityManager. */
+sealed class NetworkStatus {
+ object Available : NetworkStatus()
+
+ object Unavailable : NetworkStatus()
+}
diff --git a/photopicker/src/com/android/photopicker/extensions/Context.kt b/photopicker/src/com/android/photopicker/extensions/Context.kt
new file mode 100644
index 0000000..d6ad82b
--- /dev/null
+++ b/photopicker/src/com/android/photopicker/extensions/Context.kt
@@ -0,0 +1,31 @@
+/*
+ * Copyright 2024 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.photopicker.extensions
+
+import android.content.Context
+import androidx.core.content.getSystemService
+
+/**
+ * Extension that removes nullability of getSystemService
+ *
+ * @param T The Type of the SystemService
+ * @return A non null System service.
+ * @throws [IllegalStateException] if the returned service is null.
+ */
+inline fun <reified T> Context.requireSystemService(): T {
+ return checkNotNull(getSystemService()) { "A required System Service was null" }
+}
diff --git a/photopicker/src/com/android/photopicker/inject/ActivityModule.kt b/photopicker/src/com/android/photopicker/inject/ActivityModule.kt
new file mode 100644
index 0000000..01ef64b
--- /dev/null
+++ b/photopicker/src/com/android/photopicker/inject/ActivityModule.kt
@@ -0,0 +1,86 @@
+/*
+ * Copyright 2024 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.photopicker.core
+
+import android.app.Activity
+import android.content.Context
+import android.util.Log
+import androidx.lifecycle.Lifecycle
+import androidx.lifecycle.LifecycleOwner
+import androidx.lifecycle.lifecycleScope
+import com.android.photopicker.core.network.NetworkMonitor
+import dagger.Module
+import dagger.Provides
+import dagger.hilt.InstallIn
+import dagger.hilt.android.components.ActivityComponent
+import dagger.hilt.android.qualifiers.ActivityContext
+import kotlinx.coroutines.CoroutineScope
+
+/**
+ * Injection Module that provides access to objects bound to the [Activity]'s [Lifecycle].
+ *
+ * These can be injected by requesting the type with the [@ActivityOwned] qualifier.
+ *
+ * The module obtains a reference to the activity by installing in ActivityComponent (thus binding
+ * the scope of this module to an individual Activity instance).
+ *
+ * Note: Jobs that are launched in the [CoroutineScope] provided by this module will be
+ * automatically cancelled when the Activity's lifecycle is ended.
+ */
+@Module
+@InstallIn(ActivityComponent::class)
+class ActivityModule {
+
+ // Avoid initialization until it's actually needed.
+ private lateinit var networkMonitor: NetworkMonitor
+
+ @Provides
+ @ActivityOwned
+ fun lifecycle(activity: Activity): Lifecycle {
+ check(activity is LifecycleOwner) { "activity must implement LifecycleOwner" }
+ return activity.lifecycle
+ }
+
+ @Provides
+ @ActivityOwned
+ fun activityScope(activity: Activity): CoroutineScope {
+ check(activity is LifecycleOwner) { "activity must implement LifecycleOwner" }
+ return activity.lifecycleScope
+ }
+
+ /**
+ * Provider for the [NetworkMonitor]. This is lazily initialized only when requested to save on
+ * initialization costs of this module.
+ */
+ @Provides
+ @ActivityOwned
+ fun provideNetworkMonitor(
+ @ActivityContext context: Context,
+ @ActivityOwned scope: CoroutineScope,
+ ): NetworkMonitor {
+ if (::networkMonitor.isInitialized) {
+ return networkMonitor
+ } else {
+ Log.d(
+ NetworkMonitor.TAG,
+ "NetworkMonitor requested, but not yet initialized. Initializing NetworkMonitor."
+ )
+ networkMonitor = NetworkMonitor(context, scope)
+ return networkMonitor
+ }
+ }
+}
diff --git a/photopicker/src/com/android/photopicker/inject/ConcurrencyModule.kt b/photopicker/src/com/android/photopicker/inject/ConcurrencyModule.kt
new file mode 100644
index 0000000..b27ec56
--- /dev/null
+++ b/photopicker/src/com/android/photopicker/inject/ConcurrencyModule.kt
@@ -0,0 +1,39 @@
+/*
+ * Copyright 2024 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.photopicker.core
+
+import dagger.Module
+import dagger.Provides
+import dagger.hilt.InstallIn
+import dagger.hilt.components.SingletonComponent
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.Dispatchers
+
+/**
+ * Injection Module that provides Dispatchers for injection when scoping Coroutines. This module is
+ * installed in the [SingletonComponent] and is Application wide, not Activity wide.
+ */
+@Module
+@InstallIn(SingletonComponent::class)
+object ConcurrencyModule {
+
+ /** Injectable dispatcher to dispatch jobs that will run immediately on the main thread. */
+ @Provides @Main fun provideMainDispatcher(): CoroutineDispatcher = Dispatchers.Main.immediate
+
+ /** Injectable dispatcher to dispatch jobs that will run on [Dispatchers.IO]. */
+ @Provides @Background fun provideBackgroundDispatcher(): CoroutineDispatcher = Dispatchers.IO
+}
diff --git a/photopicker/src/com/android/photopicker/inject/Qualifiers.kt b/photopicker/src/com/android/photopicker/inject/Qualifiers.kt
new file mode 100644
index 0000000..86e687d
--- /dev/null
+++ b/photopicker/src/com/android/photopicker/inject/Qualifiers.kt
@@ -0,0 +1,28 @@
+/*
+ * Copyright 2024 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.photopicker.core
+
+import javax.inject.Qualifier
+
+/** This qualifies the injectable resource to be bound to the Activity lifecycle */
+@Qualifier @Retention(AnnotationRetention.RUNTIME) annotation class ActivityOwned
+
+/** This qualifies the injectable resource to be bound to Background work. */
+@Qualifier @Retention(AnnotationRetention.RUNTIME) annotation class Background
+
+/** This qualifies the injectable resource to be bound to Main thread work. */
+@Qualifier @Retention(AnnotationRetention.RUNTIME) annotation class Main
diff --git a/photopicker/tests/Android.bp b/photopicker/tests/Android.bp
new file mode 100644
index 0000000..e0a2ff8
--- /dev/null
+++ b/photopicker/tests/Android.bp
@@ -0,0 +1,29 @@
+android_test {
+
+ name: "PhotopickerTests",
+ test_suites: ["general-tests"],
+ manifest: "AndroidManifest.xml",
+ srcs: ["src/**/*.kt"],
+ compile_multilib: "both",
+ certificate: "media",
+ sdk_version: "module_current",
+ min_sdk_version: "30",
+ libs: [
+ "framework-connectivity.stubs.module_lib",
+ ],
+ static_libs: [
+ // sources
+ "PhotopickerLib", // test dependencies
+
+ "androidx.compose.runtime_runtime",
+ "androidx.compose.ui_ui-test-junit4",
+ "androidx.compose.ui_ui-test-manifest",
+ "androidx.test.core",
+ "androidx.test.rules",
+ "mockito-target",
+ "truth",
+ ],
+
+ aaptflags: ["--custom-package com.android.photopicker"],
+
+}
diff --git a/photopicker/tests/AndroidManifest.xml b/photopicker/tests/AndroidManifest.xml
new file mode 100644
index 0000000..ca69d60
--- /dev/null
+++ b/photopicker/tests/AndroidManifest.xml
@@ -0,0 +1,28 @@
+<!--
+ Copyright 2024 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+<manifest
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.photopicker.tests">
+
+ <application android:debuggable="true">
+ <uses-library android:name="android.test.runner"/>
+ </application>
+
+ <instrumentation
+ android:name="androidx.test.runner.AndroidJUnitRunner"
+ android:targetPackage="com.android.photopicker.tests"
+ android:label="Tests for Android Photopicker"/>
+</manifest>
diff --git a/photopicker/tests/src/com/android/photopicker/MainActivityTest.kt b/photopicker/tests/src/com/android/photopicker/MainActivityTest.kt
new file mode 100644
index 0000000..9dd415e
--- /dev/null
+++ b/photopicker/tests/src/com/android/photopicker/MainActivityTest.kt
@@ -0,0 +1,37 @@
+/*
+ * Copyright 2024 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.photopicker
+
+import androidx.compose.ui.test.assertIsDisplayed
+import androidx.compose.ui.test.junit4.createAndroidComposeRule
+import androidx.compose.ui.test.onNodeWithText
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+/** This test class will run Photopicker's actual MainActivity. */
+@RunWith(AndroidJUnit4::class)
+class MainActivityTest {
+ @get:Rule
+ val composeTestRule = createAndroidComposeRule(activityClass = MainActivity::class.java)
+
+ @Test
+ fun testMainActivity() {
+ composeTestRule.onNodeWithText("Hello World from Photopicker!").assertIsDisplayed()
+ }
+}
diff --git a/photopicker/tests/src/com/android/photopicker/core/PhotopickerAppTest.kt b/photopicker/tests/src/com/android/photopicker/core/PhotopickerAppTest.kt
new file mode 100644
index 0000000..90b5d9c
--- /dev/null
+++ b/photopicker/tests/src/com/android/photopicker/core/PhotopickerAppTest.kt
@@ -0,0 +1,40 @@
+/*
+ * Copyright 2024 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.photopicker.core
+
+import androidx.compose.ui.test.assertIsDisplayed
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.compose.ui.test.onNodeWithText
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+/** Unit tests for the main PhotopickerApp composable. */
+@RunWith(AndroidJUnit4::class)
+class PhotopickerAppTest {
+
+ @get:Rule val composeTestRule = createComposeRule()
+
+ @Test
+ fun testPhotopickerApp() {
+
+ composeTestRule.setContent { PhotopickerApp() }
+
+ composeTestRule.onNodeWithText("Hello World from Photopicker!").assertIsDisplayed()
+ }
+}
diff --git a/photopicker/tests/src/com/android/photopicker/core/network/NetworkMonitorTest.kt b/photopicker/tests/src/com/android/photopicker/core/network/NetworkMonitorTest.kt
new file mode 100644
index 0000000..10b20e6
--- /dev/null
+++ b/photopicker/tests/src/com/android/photopicker/core/network/NetworkMonitorTest.kt
@@ -0,0 +1,198 @@
+/*
+ * Copyright 2024 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.photopicker.core.network
+
+import android.content.Context
+import android.net.ConnectivityManager
+import android.net.Network
+import android.net.NetworkCapabilities
+import android.net.NetworkRequest
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.photopicker.tests.utils.mockito.capture
+import com.android.photopicker.tests.utils.mockito.mockSystemService
+import com.android.photopicker.tests.utils.mockito.whenever
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.first
+import kotlinx.coroutines.flow.toList
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.test.advanceTimeBy
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentCaptor
+import org.mockito.Captor
+import org.mockito.Mock
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+
+/** Unit tests for the [NetworkManager] */
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+@OptIn(ExperimentalCoroutinesApi::class)
+class NetworkMonitorTest {
+ lateinit var networkMonitor: NetworkMonitor
+
+ @Mock lateinit var context: Context
+ @Mock lateinit var mockNetwork: Network
+ @Mock lateinit var mockConnectivityManager: ConnectivityManager
+ @Captor lateinit var callback: ArgumentCaptor<ConnectivityManager.NetworkCallback>
+ @Captor lateinit var networkRequest: ArgumentCaptor<NetworkRequest>
+
+ @Before
+ fun setup() {
+ MockitoAnnotations.initMocks(this)
+ mockSystemService(context, ConnectivityManager::class.java) { mockConnectivityManager }
+ whenever(mockConnectivityManager.activeNetwork) { mockNetwork }
+ whenever(mockConnectivityManager.getNetworkCapabilities(mockNetwork)) {
+ NetworkCapabilities.Builder()
+ .apply { addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET) }
+ .build()
+ }
+ }
+
+ /** Ensures the initial [NetworkStatus] is emitted before any callbacks are received. */
+ @Test
+ fun testInitialNetworkIsAvailable() {
+ runTest { // this: TestScope
+ networkMonitor = NetworkMonitor(context, this.backgroundScope)
+ launch {
+ val reportedStatus = networkMonitor.networkStatus.first()
+ assertThat(reportedStatus).isEqualTo(NetworkStatus.Available)
+ }
+ }
+ }
+
+ /** Ensures the [NetworkMonitor] correctly sets up a [ConnectivityManager.NetworkCallback] */
+ @Test
+ fun testRegistersNetworkCallback() {
+ runTest {
+ networkMonitor = NetworkMonitor(context, this.backgroundScope)
+ launch {
+ val reportedStatus = networkMonitor.networkStatus.first()
+ assertThat(reportedStatus).isEqualTo(NetworkStatus.Available)
+ }
+ advanceTimeBy(100)
+ verify(mockConnectivityManager)
+ .registerNetworkCallback(capture(networkRequest), capture(callback))
+
+ val request: NetworkRequest = networkRequest.getValue()
+ val callback: ConnectivityManager.NetworkCallback = callback.getValue()
+
+ assertThat(callback).isNotNull()
+ assertThat(request.hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)).isTrue()
+ }
+ }
+
+ /**
+ * Ensures the [ConnectivityManager.NetworkCallback] emits the correct [NetworkStatus] when
+ * called.
+ */
+ @Test
+ fun testCallbackEmitsNewNetworkStatus() {
+ runTest {
+ networkMonitor = NetworkMonitor(context, this.backgroundScope)
+ val emissions = mutableListOf<NetworkStatus>()
+ backgroundScope.launch { networkMonitor.networkStatus.toList(emissions) }
+ advanceTimeBy(100)
+ verify(mockConnectivityManager)
+ .registerNetworkCallback(capture(networkRequest), capture(callback))
+
+ val callback: ConnectivityManager.NetworkCallback = callback.getValue()
+
+ assertThat(emissions.removeFirst()).isEqualTo(NetworkStatus.Available)
+
+ callback.onUnavailable()
+ advanceTimeBy(100)
+
+ assertThat(emissions.removeFirst()).isEqualTo(NetworkStatus.Unavailable)
+
+ callback.onAvailable(mockNetwork)
+ advanceTimeBy(100)
+
+ assertThat(emissions.removeFirst()).isEqualTo(NetworkStatus.Available)
+
+ callback.onLost(mockNetwork)
+ advanceTimeBy(100)
+
+ assertThat(emissions.removeFirst()).isEqualTo(NetworkStatus.Unavailable)
+ }
+ }
+
+ /** Ensures new subscribers to the flow only receive the latest [NetworkStatus] */
+ @Test
+ fun testNetworkStatusReplayedForNewSubscribers() {
+ runTest {
+ networkMonitor = NetworkMonitor(context, this.backgroundScope)
+ val allEmissions = mutableListOf<NetworkStatus>()
+ backgroundScope.launch { networkMonitor.networkStatus.toList(allEmissions) }
+ advanceTimeBy(100)
+ verify(mockConnectivityManager)
+ .registerNetworkCallback(capture(networkRequest), capture(callback))
+
+ val callback: ConnectivityManager.NetworkCallback = callback.getValue()
+
+ callback.onUnavailable()
+ advanceTimeBy(100)
+
+ callback.onAvailable(mockNetwork)
+ advanceTimeBy(100)
+
+ callback.onLost(mockNetwork)
+ advanceTimeBy(100)
+
+ assertThat(allEmissions.size).isEqualTo(4)
+
+ // Register a new collector, which should jump straight to the end of emissions.
+ val emissions = mutableListOf<NetworkStatus>()
+ backgroundScope.launch { networkMonitor.networkStatus.toList(emissions) }
+ advanceTimeBy(100)
+
+ assertThat(emissions.first()).isEqualTo(NetworkStatus.Unavailable)
+ assertThat(emissions.size).isEqualTo(1)
+ }
+ }
+
+ /** Ensures only new values are emitted from the NetworkStatus flow. */
+ @Test
+ fun testNetworkStatusIsDistinctUntilChanged() {
+ runTest {
+ networkMonitor = NetworkMonitor(context, this.backgroundScope)
+ val emissions = mutableListOf<NetworkStatus>()
+ backgroundScope.launch { networkMonitor.networkStatus.toList(emissions) }
+ advanceTimeBy(100)
+ verify(mockConnectivityManager)
+ .registerNetworkCallback(capture(networkRequest), capture(callback))
+
+ val callback: ConnectivityManager.NetworkCallback = callback.getValue()
+
+ callback.onAvailable(mockNetwork)
+ advanceTimeBy(100)
+
+ callback.onAvailable(mockNetwork)
+ advanceTimeBy(100)
+
+ callback.onAvailable(mockNetwork)
+ advanceTimeBy(100)
+
+ assertThat(emissions.first()).isEqualTo(NetworkStatus.Available)
+ assertThat(emissions.size).isEqualTo(1)
+ }
+ }
+}
diff --git a/photopicker/tests/src/com/android/photopicker/utils/MockitoUtils.kt b/photopicker/tests/src/com/android/photopicker/utils/MockitoUtils.kt
new file mode 100644
index 0000000..026823b
--- /dev/null
+++ b/photopicker/tests/src/com/android/photopicker/utils/MockitoUtils.kt
@@ -0,0 +1,55 @@
+/*
+ * Copyright 2024 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.photopicker.tests.utils.mockito
+
+import android.content.Context
+import org.mockito.ArgumentCaptor
+import org.mockito.Mockito
+import org.mockito.invocation.InvocationOnMock
+
+/**
+ * Wrapper around Mockito's when method. "when" is a protected keyword in Kotlin, so this provides a
+ * convenient wrapper to use that also includes a block for creating a .thenAnswer chained call.
+ */
+fun <Type> whenever(mock: Type, block: InvocationOnMock.() -> Type) =
+ Mockito.`when`(mock).thenAnswer { block(it) }
+
+/**
+ * Returns ArgumentCaptor.capture() as nullable type to avoid [java.lang.IllegalStateException] when
+ * null is returned.
+ *
+ * Generic T is nullable because implicitly bounded by Any?.
+ */
+fun <Type> capture(argumentCaptor: ArgumentCaptor<Type>): Type = argumentCaptor.capture()
+
+/**
+ * Registers mock returns for the designated system service. This is a Mockito helper to help with
+ * the [requireSystemService] extension for correctly mocking out services based on their type
+ * signatures.
+ *
+ * @param context The (mock) context object to stub out service on.
+ * @param classToMock The java class of the system service to be mocked.
+ * @param block A block that returns the mocked service value.
+ */
+fun <Type> mockSystemService(
+ context: Context,
+ classToMock: Class<Type>,
+ block: InvocationOnMock.() -> Type
+) {
+ whenever(context.getSystemServiceName(classToMock)) { classToMock.simpleName }
+ whenever(context.getSystemService(classToMock.simpleName)) { block() }
+}
diff --git a/res/drawable/profile_menu_background.xml b/res/drawable/profile_menu_background.xml
new file mode 100644
index 0000000..eb777c6
--- /dev/null
+++ b/res/drawable/profile_menu_background.xml
@@ -0,0 +1,6 @@
+<shape xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
+ android:shape="rectangle">
+ <corners android:radius="26dp" />
+ <solid android:color="?androidprv:attr/colorSurfaceHighlight" />
+</shape>
\ No newline at end of file
diff --git a/res/layout/activity_photo_picker.xml b/res/layout/activity_photo_picker.xml
index fc73702..daf251a 100644
--- a/res/layout/activity_photo_picker.xml
+++ b/res/layout/activity_photo_picker.xml
@@ -178,4 +178,19 @@
app:elevation="3dp"
app:icon="@drawable/ic_work_outline"/>
+ <com.google.android.material.floatingactionbutton.ExtendedFloatingActionButton
+ android:id="@+id/profile_menu_button"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginBottom="@dimen/picker_profile_button_margin_bottom"
+ android:layout_gravity="bottom|center"
+ android:textAppearance="@style/PickerButtonTextAppearance"
+ android:textColor="?attr/pickerProfileButtonTextColor"
+ android:visibility="gone"
+ android:accessibilityTraversalAfter="@+id/toolbar"
+ android:accessibilityTraversalBefore="@+id/fragment_container"
+ app:backgroundTint="?attr/pickerProfileButtonColor"
+ app:borderWidth="0dp"
+ app:elevation="3dp"/>
+
</androidx.coordinatorlayout.widget.CoordinatorLayout>
diff --git a/res/layout/profile_menu_item.xml b/res/layout/profile_menu_item.xml
new file mode 100644
index 0000000..56b695b
--- /dev/null
+++ b/res/layout/profile_menu_item.xml
@@ -0,0 +1,18 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:orientation="horizontal"
+ android:padding="8dp">
+
+ <TextView
+ android:id="@+id/profile_label"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/picker_personal_profile_label"
+ android:layout_gravity="center_vertical"
+ android:textColor="?android:attr/textColorPrimary"
+ android:drawableStart="@drawable/ic_personal_mode"
+ android:padding="8dp"/>
+</LinearLayout>
\ No newline at end of file
diff --git a/res/layout/profile_menu_layout.xml b/res/layout/profile_menu_layout.xml
new file mode 100644
index 0000000..6ebfaca
--- /dev/null
+++ b/res/layout/profile_menu_layout.xml
@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:orientation="vertical">
+
+ <LinearLayout
+ android:id="@+id/profile_menu_container"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="vertical">
+ </LinearLayout>
+
+</LinearLayout>
\ No newline at end of file
diff --git a/res/layout/userprofile_menu_item.xml b/res/layout/userprofile_menu_item.xml
new file mode 100644
index 0000000..3d971e2
--- /dev/null
+++ b/res/layout/userprofile_menu_item.xml
@@ -0,0 +1,11 @@
+<TextView
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/textViewMenuItem"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center"
+ android:gravity="center_vertical"
+ android:drawableStart="@drawable/ic_personal_mode"
+ android:text="@string/picker_personal_profile_label"
+ android:textSize="16sp"
+ android:radius="8dp" />
\ No newline at end of file
diff --git a/res/values-af/strings.xml b/res/values-af/strings.xml
index e3d1c1e..e0f27ce 100644
--- a/res/values-af/strings.xml
+++ b/res/values-af/strings.xml
@@ -66,11 +66,20 @@
<string name="picker_preview" msgid="6257414886055861039">"Voorskou"</string>
<string name="picker_work_profile" msgid="2083221066869141576">"Skakel oor na werk"</string>
<string name="picker_personal_profile" msgid="639484258397758406">"Skakel oor na persoonlik"</string>
+ <string name="picker_personal_profile_label" msgid="6189198163209597344">"Persoonlik"</string>
<string name="picker_profile_admin_title" msgid="4172022376418293777">"Deur jou admin geblokkeer"</string>
<string name="picker_profile_admin_msg_from_personal" msgid="1941639895084555723">"Toegang tot werkdata van \'n persoonlike program af word nie toegelaat nie"</string>
<string name="picker_profile_admin_msg_from_work" msgid="8048524337462790110">"Toegang tot persoonlike data van \'n werkprogram af word nie toegelaat nie"</string>
+ <!-- no translation found for picker_profile_admin_msg (4060112887923255178) -->
+ <skip />
+ <!-- no translation found for picker_profile_switch_message (1133817927412489487) -->
+ <skip />
<string name="picker_profile_work_paused_title" msgid="382212880704235925">"Werkapps is onderbreek"</string>
+ <!-- no translation found for picker_profile_paused_title (2079739512895529028) -->
+ <skip />
<string name="picker_profile_work_paused_msg" msgid="6321552322125246726">"Om werkfoto\'s oop te maak, moet jy jou werkprogramme aanskakel en weer probeer"</string>
+ <!-- no translation found for picker_profile_paused_msg (1215076898583993782) -->
+ <skip />
<string name="picker_privacy_message" msgid="9132700451027116817">"Hierdie program het net toegang tot die foto\'s wat jy kies"</string>
<string name="picker_header_permissions" msgid="675872774407768495">"Kies foto’s en video’s waartoe jy vir hierdie app toegang gee"</string>
<string name="picker_album_item_count" msgid="4420723302534177596">"{count,plural, =1{<xliff:g id="COUNT_0">^1</xliff:g> item}other{<xliff:g id="COUNT_1">^1</xliff:g> items}}"</string>
@@ -160,4 +169,8 @@
<string name="dialog_error_message" msgid="5120432204743681606">"Probeer later weer. Jou foto’s sal beskikbaar wees sodra die kwessie opgelos is."</string>
<string name="dialog_error_title" msgid="636349284077820636">"Sommige foto’s kan nie laai nie"</string>
<string name="dialog_button_text" msgid="351366485240852280">"Het dit"</string>
+ <!-- no translation found for permlab_accessMediaOwnerPackageName (3849443148060165651) -->
+ <skip />
+ <!-- no translation found for permdesc_accessMediaOwnerPackageName (7381563109363105371) -->
+ <skip />
</resources>
diff --git a/res/values-am/strings.xml b/res/values-am/strings.xml
index 32b2d97..175b5b9 100644
--- a/res/values-am/strings.xml
+++ b/res/values-am/strings.xml
@@ -66,11 +66,20 @@
<string name="picker_preview" msgid="6257414886055861039">"ቅድመ-ዕይታ"</string>
<string name="picker_work_profile" msgid="2083221066869141576">"ወደ የሥራ ቀይር"</string>
<string name="picker_personal_profile" msgid="639484258397758406">"ወደ የግል ቀይር"</string>
+ <string name="picker_personal_profile_label" msgid="6189198163209597344">"የግል"</string>
<string name="picker_profile_admin_title" msgid="4172022376418293777">"በእርስዎ አስተዳዳሪ ታግዷል"</string>
<string name="picker_profile_admin_msg_from_personal" msgid="1941639895084555723">"የስራ ውሂብን ከግል መተግበሪያ መድረስ አይፈቀድም"</string>
<string name="picker_profile_admin_msg_from_work" msgid="8048524337462790110">"የግል ውሂብን ከሥራ መተግበሪያ መድረስ አይፈቀድም"</string>
+ <!-- no translation found for picker_profile_admin_msg (4060112887923255178) -->
+ <skip />
+ <!-- no translation found for picker_profile_switch_message (1133817927412489487) -->
+ <skip />
<string name="picker_profile_work_paused_title" msgid="382212880704235925">"የስራ መተግበሪያዎች ባሉበት ቆመዋል"</string>
+ <!-- no translation found for picker_profile_paused_title (2079739512895529028) -->
+ <skip />
<string name="picker_profile_work_paused_msg" msgid="6321552322125246726">"የሥራ ፎቶዎችን ለመክፈት የሥራ መተግበሪያዎችዎን ያብሩ እና እንደገና ይሞክሩ"</string>
+ <!-- no translation found for picker_profile_paused_msg (1215076898583993782) -->
+ <skip />
<string name="picker_privacy_message" msgid="9132700451027116817">"ይህ መተግበሪያ መድረስ የሚችለው እርስዎ የሚመርጧቸውን ፎቶዎች ብቻ ነው"</string>
<string name="picker_header_permissions" msgid="675872774407768495">"ይህ መዳረሻ እንዲደርስባቸው የሚፈቅዱባቸውን ፎቶዎች እና ቪድዮዎች ይምረጡ"</string>
<string name="picker_album_item_count" msgid="4420723302534177596">"{count,plural, =1{<xliff:g id="COUNT_0">^1</xliff:g> ንጥል}one{<xliff:g id="COUNT_1">^1</xliff:g> ንጥል}other{<xliff:g id="COUNT_1">^1</xliff:g> ንጥሎች}}"</string>
@@ -160,4 +169,8 @@
<string name="dialog_error_message" msgid="5120432204743681606">"ቆይተው እንደገና ይሞክሩ። የእርስዎ ፎቶዎች አንዴ ችግሩ ከተፈታ በኋላ ይገኛሉ።"</string>
<string name="dialog_error_title" msgid="636349284077820636">"አንዳንድ ፎቶዎችን መጫን አይቻለም"</string>
<string name="dialog_button_text" msgid="351366485240852280">"ገባኝ"</string>
+ <!-- no translation found for permlab_accessMediaOwnerPackageName (3849443148060165651) -->
+ <skip />
+ <!-- no translation found for permdesc_accessMediaOwnerPackageName (7381563109363105371) -->
+ <skip />
</resources>
diff --git a/res/values-ar/strings.xml b/res/values-ar/strings.xml
index 9ff9503..7585b79 100644
--- a/res/values-ar/strings.xml
+++ b/res/values-ar/strings.xml
@@ -66,11 +66,20 @@
<string name="picker_preview" msgid="6257414886055861039">"معاينة"</string>
<string name="picker_work_profile" msgid="2083221066869141576">"التبديل إلى الملف الشخصي للعمل"</string>
<string name="picker_personal_profile" msgid="639484258397758406">"التبديل إلى الملف الشخصي"</string>
+ <string name="picker_personal_profile_label" msgid="6189198163209597344">"شخصي"</string>
<string name="picker_profile_admin_title" msgid="4172022376418293777">"حظر المشرف هذه الميزة"</string>
<string name="picker_profile_admin_msg_from_personal" msgid="1941639895084555723">"غير مسموح بالوصول إلى بيانات العمل من تطبيق شخصي."</string>
<string name="picker_profile_admin_msg_from_work" msgid="8048524337462790110">"غير مسموح بالوصول إلى البيانات الشخصية من تطبيق عمل."</string>
+ <!-- no translation found for picker_profile_admin_msg (4060112887923255178) -->
+ <skip />
+ <!-- no translation found for picker_profile_switch_message (1133817927412489487) -->
+ <skip />
<string name="picker_profile_work_paused_title" msgid="382212880704235925">"تطبيقات العمل متوقفة مؤقتًا"</string>
+ <!-- no translation found for picker_profile_paused_title (2079739512895529028) -->
+ <skip />
<string name="picker_profile_work_paused_msg" msgid="6321552322125246726">"لفتح صور العمل، عليك تفعيل تطبيقات العمل ثم إعادة المحاولة."</string>
+ <!-- no translation found for picker_profile_paused_msg (1215076898583993782) -->
+ <skip />
<string name="picker_privacy_message" msgid="9132700451027116817">"يمكن لهذا التطبيق الوصول إلى الصور التي تختارها فقط."</string>
<string name="picker_header_permissions" msgid="675872774407768495">"اختَر الصور والفيديوهات التي تريد السماح لهذا التطبيق بالوصول إليها"</string>
<string name="picker_album_item_count" msgid="4420723302534177596">"{count,plural, =1{صورة واحدة (<xliff:g id="COUNT_0">^1</xliff:g>)}zero{<xliff:g id="COUNT_1">^1</xliff:g> صورة}two{صورتان (<xliff:g id="COUNT_1">^1</xliff:g>)}few{<xliff:g id="COUNT_1">^1</xliff:g> صور}many{<xliff:g id="COUNT_1">^1</xliff:g> صورة}other{<xliff:g id="COUNT_1">^1</xliff:g> صورة}}"</string>
@@ -160,4 +169,8 @@
<string name="dialog_error_message" msgid="5120432204743681606">"يُرجى إعادة المحاولة لاحقًا. ستتوفّر صورك عند حل المشكلة."</string>
<string name="dialog_error_title" msgid="636349284077820636">"يتعذّر تحميل بعض الصور"</string>
<string name="dialog_button_text" msgid="351366485240852280">"حسنًا"</string>
+ <!-- no translation found for permlab_accessMediaOwnerPackageName (3849443148060165651) -->
+ <skip />
+ <!-- no translation found for permdesc_accessMediaOwnerPackageName (7381563109363105371) -->
+ <skip />
</resources>
diff --git a/res/values-as/strings.xml b/res/values-as/strings.xml
index f97b5c1..aead5a3 100644
--- a/res/values-as/strings.xml
+++ b/res/values-as/strings.xml
@@ -66,11 +66,20 @@
<string name="picker_preview" msgid="6257414886055861039">"পূৰ্বদৰ্শন কৰক"</string>
<string name="picker_work_profile" msgid="2083221066869141576">"কৰ্মস্থানৰ প্ৰ’ফাইললৈ সলনি কৰক"</string>
<string name="picker_personal_profile" msgid="639484258397758406">"ব্যক্তিগত প্ৰ’ফাইললৈ সলনি কৰক"</string>
+ <string name="picker_personal_profile_label" msgid="6189198163209597344">"ব্যক্তিগত"</string>
<string name="picker_profile_admin_title" msgid="4172022376418293777">"আপোনাৰ প্ৰশাসকে অৱৰোধ কৰিছে"</string>
<string name="picker_profile_admin_msg_from_personal" msgid="1941639895084555723">"ব্যক্তিগত এপৰ পৰা কৰ্মস্থানৰ ডেটা এক্সেছ কৰাৰ অনুমতি নাই"</string>
<string name="picker_profile_admin_msg_from_work" msgid="8048524337462790110">"কাম সম্পর্কীয় এপৰ পৰা ব্যক্তিগত ডেটা এক্সেছ কৰাৰ অনুমতি নাই"</string>
+ <!-- no translation found for picker_profile_admin_msg (4060112887923255178) -->
+ <skip />
+ <!-- no translation found for picker_profile_switch_message (1133817927412489487) -->
+ <skip />
<string name="picker_profile_work_paused_title" msgid="382212880704235925">"কাম সম্পর্কীয় এপ্সমূহ পজ কৰা আছে"</string>
+ <!-- no translation found for picker_profile_paused_title (2079739512895529028) -->
+ <skip />
<string name="picker_profile_work_paused_msg" msgid="6321552322125246726">"কৰ্মস্থানৰ ফট’ খুলিবলৈ আপোনাৰ কাম সম্পর্কীয় এপ্সমূহ অন কৰক তাৰ পাছত পুনৰ চেষ্টা কৰক"</string>
+ <!-- no translation found for picker_profile_paused_msg (1215076898583993782) -->
+ <skip />
<string name="picker_privacy_message" msgid="9132700451027116817">"এই এপ্টোৱে কেৱল আপুনি বাছনি কৰা ফট\'সমূহ এক্সেছ কৰিব পাৰে"</string>
<string name="picker_header_permissions" msgid="675872774407768495">"আপুনি এই এপ্টোক এক্সেছ কৰিবলৈ দিয়া ফট’ আৰু ভিডিঅ’সমূহ বাছনি কৰক"</string>
<string name="picker_album_item_count" msgid="4420723302534177596">"{count,plural, =1{<xliff:g id="COUNT_0">^1</xliff:g> টা বস্তু}one{<xliff:g id="COUNT_1">^1</xliff:g> টা বস্তু}other{<xliff:g id="COUNT_1">^1</xliff:g> টা বস্তু}}"</string>
@@ -160,4 +169,8 @@
<string name="dialog_error_message" msgid="5120432204743681606">"পাছত পুনৰ চেষ্টা কৰক। সমস্যাটো সমাধান হোৱাৰ পাছত আপোনাৰ ফট’সমূহ উপলব্ধ হ’ব।"</string>
<string name="dialog_error_title" msgid="636349284077820636">"কিছুমান ফট’ ল’ড কৰিব নোৱাৰি"</string>
<string name="dialog_button_text" msgid="351366485240852280">"বুজি পালোঁ"</string>
+ <!-- no translation found for permlab_accessMediaOwnerPackageName (3849443148060165651) -->
+ <skip />
+ <!-- no translation found for permdesc_accessMediaOwnerPackageName (7381563109363105371) -->
+ <skip />
</resources>
diff --git a/res/values-az/strings.xml b/res/values-az/strings.xml
index 88c4c05..713ee83 100644
--- a/res/values-az/strings.xml
+++ b/res/values-az/strings.xml
@@ -66,11 +66,20 @@
<string name="picker_preview" msgid="6257414886055861039">"Önbaxış"</string>
<string name="picker_work_profile" msgid="2083221066869141576">"İş profilinə keçirin"</string>
<string name="picker_personal_profile" msgid="639484258397758406">"Şəxsi profilə keçirin"</string>
+ <string name="picker_personal_profile_label" msgid="6189198163209597344">"Şəxsi"</string>
<string name="picker_profile_admin_title" msgid="4172022376418293777">"Admininiz tərəfindən bloklanıb"</string>
<string name="picker_profile_admin_msg_from_personal" msgid="1941639895084555723">"Şəxsi tətbiqdən iş datasına girişə icazə verilmir"</string>
<string name="picker_profile_admin_msg_from_work" msgid="8048524337462790110">"İş tətbiqindən şəxsi dataya girişə icazə verilmir"</string>
+ <!-- no translation found for picker_profile_admin_msg (4060112887923255178) -->
+ <skip />
+ <!-- no translation found for picker_profile_switch_message (1133817927412489487) -->
+ <skip />
<string name="picker_profile_work_paused_title" msgid="382212880704235925">"İş tətbiqləri durdurulub"</string>
+ <!-- no translation found for picker_profile_paused_title (2079739512895529028) -->
+ <skip />
<string name="picker_profile_work_paused_msg" msgid="6321552322125246726">"İş şəkillərini açmaq üçün iş tətbiqlərinizi aktiv edib yenidən cəhd edin"</string>
+ <!-- no translation found for picker_profile_paused_msg (1215076898583993782) -->
+ <skip />
<string name="picker_privacy_message" msgid="9132700451027116817">"Bu tətbiq yalnız seçdiyiniz fotolara giriş edə bilər"</string>
<string name="picker_header_permissions" msgid="675872774407768495">"Bu tətbiqin daxil olmasına icazə verdiyiniz foto və videolar seçin"</string>
<string name="picker_album_item_count" msgid="4420723302534177596">"{count,plural, =1{<xliff:g id="COUNT_0">^1</xliff:g> element}other{<xliff:g id="COUNT_1">^1</xliff:g> element}}"</string>
@@ -160,4 +169,8 @@
<string name="dialog_error_message" msgid="5120432204743681606">"Sonra cəhd edin. Problem həll edildikdən sonra fotolar əlçatan olacaq."</string>
<string name="dialog_error_title" msgid="636349284077820636">"Bəzi fotolar yüklənmir"</string>
<string name="dialog_button_text" msgid="351366485240852280">"Anladım"</string>
+ <!-- no translation found for permlab_accessMediaOwnerPackageName (3849443148060165651) -->
+ <skip />
+ <!-- no translation found for permdesc_accessMediaOwnerPackageName (7381563109363105371) -->
+ <skip />
</resources>
diff --git a/res/values-b+sr+Latn/strings.xml b/res/values-b+sr+Latn/strings.xml
index 0c40717..23c97e6 100644
--- a/res/values-b+sr+Latn/strings.xml
+++ b/res/values-b+sr+Latn/strings.xml
@@ -66,11 +66,20 @@
<string name="picker_preview" msgid="6257414886055861039">"Pregled"</string>
<string name="picker_work_profile" msgid="2083221066869141576">"Pređi na poslovni profil"</string>
<string name="picker_personal_profile" msgid="639484258397758406">"Pređi na lični profil"</string>
+ <string name="picker_personal_profile_label" msgid="6189198163209597344">"Lično"</string>
<string name="picker_profile_admin_title" msgid="4172022376418293777">"Blokira administrator"</string>
<string name="picker_profile_admin_msg_from_personal" msgid="1941639895084555723">"Pristup podacima o poslu iz lične aplikacije nije dozvoljen"</string>
<string name="picker_profile_admin_msg_from_work" msgid="8048524337462790110">"Pristup ličnim podacima iz poslovne aplikacije nije dozvoljen"</string>
+ <!-- no translation found for picker_profile_admin_msg (4060112887923255178) -->
+ <skip />
+ <!-- no translation found for picker_profile_switch_message (1133817927412489487) -->
+ <skip />
<string name="picker_profile_work_paused_title" msgid="382212880704235925">"Poslovne aplikacije su pauzirane"</string>
+ <!-- no translation found for picker_profile_paused_title (2079739512895529028) -->
+ <skip />
<string name="picker_profile_work_paused_msg" msgid="6321552322125246726">"Da biste otvorili poslovne slike, uključite poslovne aplikacije, pa probajte ponovo"</string>
+ <!-- no translation found for picker_profile_paused_msg (1215076898583993782) -->
+ <skip />
<string name="picker_privacy_message" msgid="9132700451027116817">"Ova aplikacija može da pristupa samo slikama koje izaberete"</string>
<string name="picker_header_permissions" msgid="675872774407768495">"Izaberite slike i video snimke za koje ovoj aplikaciji omogućavate pristup"</string>
<string name="picker_album_item_count" msgid="4420723302534177596">"{count,plural, =1{<xliff:g id="COUNT_0">^1</xliff:g> stavka}one{<xliff:g id="COUNT_1">^1</xliff:g> stavka}few{<xliff:g id="COUNT_1">^1</xliff:g> stavke}other{<xliff:g id="COUNT_1">^1</xliff:g> stavki}}"</string>
@@ -160,4 +169,8 @@
<string name="dialog_error_message" msgid="5120432204743681606">"Probajte ponovo kasnije. Slike će biti dostupne kada se problem reši."</string>
<string name="dialog_error_title" msgid="636349284077820636">"Učitavanje nekih slika nije uspelo"</string>
<string name="dialog_button_text" msgid="351366485240852280">"Važi"</string>
+ <!-- no translation found for permlab_accessMediaOwnerPackageName (3849443148060165651) -->
+ <skip />
+ <!-- no translation found for permdesc_accessMediaOwnerPackageName (7381563109363105371) -->
+ <skip />
</resources>
diff --git a/res/values-be/strings.xml b/res/values-be/strings.xml
index 23af8f2..1636fcc 100644
--- a/res/values-be/strings.xml
+++ b/res/values-be/strings.xml
@@ -66,11 +66,20 @@
<string name="picker_preview" msgid="6257414886055861039">"Перадпрагляд"</string>
<string name="picker_work_profile" msgid="2083221066869141576">"Пераключыцца на працоўны"</string>
<string name="picker_personal_profile" msgid="639484258397758406">"Пераключыцца на асабісты"</string>
+ <string name="picker_personal_profile_label" msgid="6189198163209597344">"Асабісты"</string>
<string name="picker_profile_admin_title" msgid="4172022376418293777">"Заблакіравана вашым адміністратарам"</string>
<string name="picker_profile_admin_msg_from_personal" msgid="1941639895084555723">"Доступ да працоўных даных з асабістай праграмы забаронены"</string>
<string name="picker_profile_admin_msg_from_work" msgid="8048524337462790110">"Доступ да асабістых даных з працоўнай праграмы забаронены"</string>
+ <!-- no translation found for picker_profile_admin_msg (4060112887923255178) -->
+ <skip />
+ <!-- no translation found for picker_profile_switch_message (1133817927412489487) -->
+ <skip />
<string name="picker_profile_work_paused_title" msgid="382212880704235925">"Працоўныя праграмы прыпынены"</string>
+ <!-- no translation found for picker_profile_paused_title (2079739512895529028) -->
+ <skip />
<string name="picker_profile_work_paused_msg" msgid="6321552322125246726">"Каб адкрыць працоўныя фота, уключыце працоўныя праграмы і паўтарыце спробу"</string>
+ <!-- no translation found for picker_profile_paused_msg (1215076898583993782) -->
+ <skip />
<string name="picker_privacy_message" msgid="9132700451027116817">"Гэта праграма можа мець доступ толькі да выбраных вамі фота"</string>
<string name="picker_header_permissions" msgid="675872774407768495">"Выберыце фота і відэа, да якіх гэта праграма будзе мець доступ"</string>
<string name="picker_album_item_count" msgid="4420723302534177596">"{count,plural, =1{<xliff:g id="COUNT_0">^1</xliff:g> элемент}one{<xliff:g id="COUNT_1">^1</xliff:g> элемент}few{<xliff:g id="COUNT_1">^1</xliff:g> элементы}many{<xliff:g id="COUNT_1">^1</xliff:g> элементаў}other{<xliff:g id="COUNT_1">^1</xliff:g> элемента}}"</string>
@@ -160,4 +169,8 @@
<string name="dialog_error_message" msgid="5120432204743681606">"Паўтарыце спробу пазней. Калі праблема будзе вырашана, вашы фота стануць даступнымі."</string>
<string name="dialog_error_title" msgid="636349284077820636">"Некаторыя фота не ўдалося загрузіць"</string>
<string name="dialog_button_text" msgid="351366485240852280">"OK"</string>
+ <!-- no translation found for permlab_accessMediaOwnerPackageName (3849443148060165651) -->
+ <skip />
+ <!-- no translation found for permdesc_accessMediaOwnerPackageName (7381563109363105371) -->
+ <skip />
</resources>
diff --git a/res/values-bg/strings.xml b/res/values-bg/strings.xml
index 9ff2d77..87e3610 100644
--- a/res/values-bg/strings.xml
+++ b/res/values-bg/strings.xml
@@ -66,11 +66,20 @@
<string name="picker_preview" msgid="6257414886055861039">"Визуализация"</string>
<string name="picker_work_profile" msgid="2083221066869141576">"Превкл. към служ. пoтр. профил"</string>
<string name="picker_personal_profile" msgid="639484258397758406">"Превкл. към личния потр. профил"</string>
+ <string name="picker_personal_profile_label" msgid="6189198163209597344">"Личен"</string>
<string name="picker_profile_admin_title" msgid="4172022376418293777">"Блокирано от администратора ви"</string>
<string name="picker_profile_admin_msg_from_personal" msgid="1941639895084555723">"Достъпът до служебни данни от лично приложение не е разрешен"</string>
<string name="picker_profile_admin_msg_from_work" msgid="8048524337462790110">"Достъпът до лични данни от служебно приложение не е разрешен"</string>
+ <!-- no translation found for picker_profile_admin_msg (4060112887923255178) -->
+ <skip />
+ <!-- no translation found for picker_profile_switch_message (1133817927412489487) -->
+ <skip />
<string name="picker_profile_work_paused_title" msgid="382212880704235925">"Служебните приложения са поставени на пауза"</string>
+ <!-- no translation found for picker_profile_paused_title (2079739512895529028) -->
+ <skip />
<string name="picker_profile_work_paused_msg" msgid="6321552322125246726">"За да отворите служебни снимки, включете служебните си приложения и опитайте отново"</string>
+ <!-- no translation found for picker_profile_paused_msg (1215076898583993782) -->
+ <skip />
<string name="picker_privacy_message" msgid="9132700451027116817">"Това приложение има достъп само до избраните от вас снимки"</string>
<string name="picker_header_permissions" msgid="675872774407768495">"Изберете снимки и видеоклипове, до които приложението да има достъп"</string>
<string name="picker_album_item_count" msgid="4420723302534177596">"{count,plural, =1{<xliff:g id="COUNT_0">^1</xliff:g> елемент}other{<xliff:g id="COUNT_1">^1</xliff:g> елемента}}"</string>
@@ -160,4 +169,8 @@
<string name="dialog_error_message" msgid="5120432204743681606">"Опитайте отново по-късно. Снимките ви ще бъдат налице, след като проблемът бъде разрешен."</string>
<string name="dialog_error_title" msgid="636349284077820636">"Някои снимки не могат да се заредят"</string>
<string name="dialog_button_text" msgid="351366485240852280">"Разбрах"</string>
+ <!-- no translation found for permlab_accessMediaOwnerPackageName (3849443148060165651) -->
+ <skip />
+ <!-- no translation found for permdesc_accessMediaOwnerPackageName (7381563109363105371) -->
+ <skip />
</resources>
diff --git a/res/values-bn/strings.xml b/res/values-bn/strings.xml
index 0d9f12d..15b8f02 100644
--- a/res/values-bn/strings.xml
+++ b/res/values-bn/strings.xml
@@ -66,11 +66,20 @@
<string name="picker_preview" msgid="6257414886055861039">"প্রিভিউ"</string>
<string name="picker_work_profile" msgid="2083221066869141576">"অফিস প্রোফাইলে সুইচ করুন"</string>
<string name="picker_personal_profile" msgid="639484258397758406">"ব্যক্তিগত প্রোফাইলে সুইচ করুন"</string>
+ <string name="picker_personal_profile_label" msgid="6189198163209597344">"ব্যক্তিগত"</string>
<string name="picker_profile_admin_title" msgid="4172022376418293777">"আপনার অ্যাডমিন ব্লক করে দিয়েছে"</string>
<string name="picker_profile_admin_msg_from_personal" msgid="1941639895084555723">"ব্যক্তিগত অ্যাপ থেকে অফিসের ডেটা অ্যাক্সেস করার অনুমতি নেই"</string>
<string name="picker_profile_admin_msg_from_work" msgid="8048524337462790110">"অফিস অ্যাপ থেকে ব্যক্তিগত ডেটা অ্যাক্সেস করার অনুমতি নেই"</string>
+ <!-- no translation found for picker_profile_admin_msg (4060112887923255178) -->
+ <skip />
+ <!-- no translation found for picker_profile_switch_message (1133817927412489487) -->
+ <skip />
<string name="picker_profile_work_paused_title" msgid="382212880704235925">"অফিসের অ্যাপ পজ করা আছে"</string>
+ <!-- no translation found for picker_profile_paused_title (2079739512895529028) -->
+ <skip />
<string name="picker_profile_work_paused_msg" msgid="6321552322125246726">"অফিস প্রোফাইলের ফটো দেখতে, অফিসের অ্যাপ চালু করে আবার চেষ্টা করুন"</string>
+ <!-- no translation found for picker_profile_paused_msg (1215076898583993782) -->
+ <skip />
<string name="picker_privacy_message" msgid="9132700451027116817">"এই অ্যাপ শুধুমাত্র আপনার বেছে নেওয়া ছবি অ্যাক্সেস করতে পারবে"</string>
<string name="picker_header_permissions" msgid="675872774407768495">"এই অ্যাপকে যেসব ফটো বা ভিডি অ্যাক্সেস করার জন্য অনুমতি দিতে চান তা বেছে নিন"</string>
<string name="picker_album_item_count" msgid="4420723302534177596">"{count,plural, =1{<xliff:g id="COUNT_0">^1</xliff:g>টি আইটেম}one{<xliff:g id="COUNT_1">^1</xliff:g>টি আইটেম}other{<xliff:g id="COUNT_1">^1</xliff:g>টি আইটেম}}"</string>
@@ -160,4 +169,8 @@
<string name="dialog_error_message" msgid="5120432204743681606">"পরে আবার চেষ্টা করুন। সমস্যার সমাধান হয়ে গেলে আপনার ফটো উপলভ্য হবে।"</string>
<string name="dialog_error_title" msgid="636349284077820636">"কিছু ফটো লোড করা যাচ্ছে না"</string>
<string name="dialog_button_text" msgid="351366485240852280">"বুঝেছি"</string>
+ <!-- no translation found for permlab_accessMediaOwnerPackageName (3849443148060165651) -->
+ <skip />
+ <!-- no translation found for permdesc_accessMediaOwnerPackageName (7381563109363105371) -->
+ <skip />
</resources>
diff --git a/res/values-bs/strings.xml b/res/values-bs/strings.xml
index 557107e..5da535c 100644
--- a/res/values-bs/strings.xml
+++ b/res/values-bs/strings.xml
@@ -66,11 +66,20 @@
<string name="picker_preview" msgid="6257414886055861039">"Pregled"</string>
<string name="picker_work_profile" msgid="2083221066869141576">"Prebacite se na radni profil"</string>
<string name="picker_personal_profile" msgid="639484258397758406">"Prebacite se na lični profil profil"</string>
+ <string name="picker_personal_profile_label" msgid="6189198163209597344">"Lično"</string>
<string name="picker_profile_admin_title" msgid="4172022376418293777">"Blokirao je administrator"</string>
<string name="picker_profile_admin_msg_from_personal" msgid="1941639895084555723">"Pristupanje poslovnim podacima iz lične aplikacije nije dozvoljeno"</string>
<string name="picker_profile_admin_msg_from_work" msgid="8048524337462790110">"Pristupanje ličnim podacima iz poslovne aplikacije nije dozvoljeno"</string>
+ <!-- no translation found for picker_profile_admin_msg (4060112887923255178) -->
+ <skip />
+ <!-- no translation found for picker_profile_switch_message (1133817927412489487) -->
+ <skip />
<string name="picker_profile_work_paused_title" msgid="382212880704235925">"Poslovne aplikacije su pauzirane"</string>
+ <!-- no translation found for picker_profile_paused_title (2079739512895529028) -->
+ <skip />
<string name="picker_profile_work_paused_msg" msgid="6321552322125246726">"Da otvorite poslovne fotografije, uključite poslovne aplikacije i pokušajte ponovo"</string>
+ <!-- no translation found for picker_profile_paused_msg (1215076898583993782) -->
+ <skip />
<string name="picker_privacy_message" msgid="9132700451027116817">"Ova aplikacija može pristupiti samo fotografijama koje odaberete"</string>
<string name="picker_header_permissions" msgid="675872774407768495">"Odaberite fotografije i videozapise kojima će aplikacija moći pristupiti"</string>
<string name="picker_album_item_count" msgid="4420723302534177596">"{count,plural, =1{<xliff:g id="COUNT_0">^1</xliff:g> stavka}one{<xliff:g id="COUNT_1">^1</xliff:g> stavka}few{<xliff:g id="COUNT_1">^1</xliff:g> stavke}other{<xliff:g id="COUNT_1">^1</xliff:g> stavki}}"</string>
@@ -160,4 +169,8 @@
<string name="dialog_error_message" msgid="5120432204743681606">"Pokušajte ponovo kasnije. Fotografije će biti dostupne čim se problem riješi."</string>
<string name="dialog_error_title" msgid="636349284077820636">"Nije moguće učitati određene fotografije"</string>
<string name="dialog_button_text" msgid="351366485240852280">"Razumijem"</string>
+ <!-- no translation found for permlab_accessMediaOwnerPackageName (3849443148060165651) -->
+ <skip />
+ <!-- no translation found for permdesc_accessMediaOwnerPackageName (7381563109363105371) -->
+ <skip />
</resources>
diff --git a/res/values-ca/strings.xml b/res/values-ca/strings.xml
index 704fc78..a15bb9d 100644
--- a/res/values-ca/strings.xml
+++ b/res/values-ca/strings.xml
@@ -66,11 +66,20 @@
<string name="picker_preview" msgid="6257414886055861039">"Previsualitza"</string>
<string name="picker_work_profile" msgid="2083221066869141576">"Canvia al perfil de treball"</string>
<string name="picker_personal_profile" msgid="639484258397758406">"Canvia al perfil personal"</string>
+ <string name="picker_personal_profile_label" msgid="6189198163209597344">"Personal"</string>
<string name="picker_profile_admin_title" msgid="4172022376418293777">"Bloquejat per l\'administrador"</string>
<string name="picker_profile_admin_msg_from_personal" msgid="1941639895084555723">"No està permès accedir a les dades de treball des d\'una aplicació personal"</string>
<string name="picker_profile_admin_msg_from_work" msgid="8048524337462790110">"No està permès accedir a les dades personals des d\'una aplicació de treball"</string>
+ <!-- no translation found for picker_profile_admin_msg (4060112887923255178) -->
+ <skip />
+ <!-- no translation found for picker_profile_switch_message (1133817927412489487) -->
+ <skip />
<string name="picker_profile_work_paused_title" msgid="382212880704235925">"Les aplicacions de treball estan en pausa"</string>
+ <!-- no translation found for picker_profile_paused_title (2079739512895529028) -->
+ <skip />
<string name="picker_profile_work_paused_msg" msgid="6321552322125246726">"Per obrir les fotos de treball, activa les aplicacions de treball i torna-ho a provar"</string>
+ <!-- no translation found for picker_profile_paused_msg (1215076898583993782) -->
+ <skip />
<string name="picker_privacy_message" msgid="9132700451027116817">"Aquesta aplicació només pot accedir a les fotos que seleccionis"</string>
<string name="picker_header_permissions" msgid="675872774407768495">"Selecciona a quines fotos i vídeos permets que accedeixi aquesta aplicació"</string>
<string name="picker_album_item_count" msgid="4420723302534177596">"{count,plural, =1{<xliff:g id="COUNT_0">^1</xliff:g> element}many{<xliff:g id="COUNT_1">^1</xliff:g> elements}other{<xliff:g id="COUNT_1">^1</xliff:g> elements}}"</string>
@@ -160,4 +169,6 @@
<string name="dialog_error_message" msgid="5120432204743681606">"Torna-ho a provar més tard. Les teves fotos estaran disponibles un cop el problema s\'hagi resolt."</string>
<string name="dialog_error_title" msgid="636349284077820636">"No es poden carregar algunes fotos"</string>
<string name="dialog_button_text" msgid="351366485240852280">"Entesos"</string>
+ <string name="permlab_accessMediaOwnerPackageName" msgid="3849443148060165651">"Accedeix als noms de paquet del propietari en fitxers multimèdia"</string>
+ <string name="permdesc_accessMediaOwnerPackageName" msgid="7381563109363105371">"Permet que l\'aplicació accedeixi als noms de paquet del propietari de tots els fitxers multimèdia accessibles."</string>
</resources>
diff --git a/res/values-cs/strings.xml b/res/values-cs/strings.xml
index dd3f2af..e8bdfe8 100644
--- a/res/values-cs/strings.xml
+++ b/res/values-cs/strings.xml
@@ -66,11 +66,20 @@
<string name="picker_preview" msgid="6257414886055861039">"Náhled"</string>
<string name="picker_work_profile" msgid="2083221066869141576">"Přepnout na pracovní profil"</string>
<string name="picker_personal_profile" msgid="639484258397758406">"Přepnout na osobní profil"</string>
+ <string name="picker_personal_profile_label" msgid="6189198163209597344">"Osobní"</string>
<string name="picker_profile_admin_title" msgid="4172022376418293777">"Blokováno administrátorem"</string>
<string name="picker_profile_admin_msg_from_personal" msgid="1941639895084555723">"Z osobní aplikace není přístup k pracovním datům povolen"</string>
<string name="picker_profile_admin_msg_from_work" msgid="8048524337462790110">"Z pracovní aplikace není přístup k osobním datům povolen"</string>
+ <!-- no translation found for picker_profile_admin_msg (4060112887923255178) -->
+ <skip />
+ <!-- no translation found for picker_profile_switch_message (1133817927412489487) -->
+ <skip />
<string name="picker_profile_work_paused_title" msgid="382212880704235925">"Pracovní aplikace jsou pozastaveny"</string>
+ <!-- no translation found for picker_profile_paused_title (2079739512895529028) -->
+ <skip />
<string name="picker_profile_work_paused_msg" msgid="6321552322125246726">"Pokud chcete otevřít pracovní fotky, zapněte pracovní aplikace a zkuste to znovu"</string>
+ <!-- no translation found for picker_profile_paused_msg (1215076898583993782) -->
+ <skip />
<string name="picker_privacy_message" msgid="9132700451027116817">"Tato aplikace má přístup pouze k fotkám, které vyberete"</string>
<string name="picker_header_permissions" msgid="675872774407768495">"Vyberte fotky a videa, ke kterým bude mít tato aplikace přístup"</string>
<string name="picker_album_item_count" msgid="4420723302534177596">"{count,plural, =1{<xliff:g id="COUNT_0">^1</xliff:g> položka}few{<xliff:g id="COUNT_1">^1</xliff:g> položky}many{<xliff:g id="COUNT_1">^1</xliff:g> položky}other{<xliff:g id="COUNT_1">^1</xliff:g> položek}}"</string>
@@ -160,4 +169,8 @@
<string name="dialog_error_message" msgid="5120432204743681606">"Zkuste to později. Fotky budou k dispozici po vyřešení tohoto problému."</string>
<string name="dialog_error_title" msgid="636349284077820636">"Některé fotografie nelze načíst"</string>
<string name="dialog_button_text" msgid="351366485240852280">"Rozumím"</string>
+ <!-- no translation found for permlab_accessMediaOwnerPackageName (3849443148060165651) -->
+ <skip />
+ <!-- no translation found for permdesc_accessMediaOwnerPackageName (7381563109363105371) -->
+ <skip />
</resources>
diff --git a/res/values-da/strings.xml b/res/values-da/strings.xml
index fbf5071..14f2e59 100644
--- a/res/values-da/strings.xml
+++ b/res/values-da/strings.xml
@@ -66,11 +66,20 @@
<string name="picker_preview" msgid="6257414886055861039">"Forhåndsvisning"</string>
<string name="picker_work_profile" msgid="2083221066869141576">"Skift til arbejdsprofil"</string>
<string name="picker_personal_profile" msgid="639484258397758406">"Skift til personlig profil"</string>
+ <string name="picker_personal_profile_label" msgid="6189198163209597344">"Personlig"</string>
<string name="picker_profile_admin_title" msgid="4172022376418293777">"Blokeret af din administrator"</string>
<string name="picker_profile_admin_msg_from_personal" msgid="1941639895084555723">"Det er ikke tilladt at tilgå arbejdsrelaterede data fra en personlig app"</string>
<string name="picker_profile_admin_msg_from_work" msgid="8048524337462790110">"Det er ikke tilladt at tilgå personoplysninger fra en arbejdsapp"</string>
+ <!-- no translation found for picker_profile_admin_msg (4060112887923255178) -->
+ <skip />
+ <!-- no translation found for picker_profile_switch_message (1133817927412489487) -->
+ <skip />
<string name="picker_profile_work_paused_title" msgid="382212880704235925">"Dine arbejdsapps er sat på pause"</string>
+ <!-- no translation found for picker_profile_paused_title (2079739512895529028) -->
+ <skip />
<string name="picker_profile_work_paused_msg" msgid="6321552322125246726">"Hvis du vil åbne billeder fra arbejdsprofilen, skal du aktivere dine arbejdsapps og derefter prøve igen"</string>
+ <!-- no translation found for picker_profile_paused_msg (1215076898583993782) -->
+ <skip />
<string name="picker_privacy_message" msgid="9132700451027116817">"Denne app kan kun tilgå de billeder, du vælger"</string>
<string name="picker_header_permissions" msgid="675872774407768495">"Vælg de billeder og videoer, du vil give denne app adgang til"</string>
<string name="picker_album_item_count" msgid="4420723302534177596">"{count,plural, =1{<xliff:g id="COUNT_0">^1</xliff:g> element}one{<xliff:g id="COUNT_1">^1</xliff:g> element}other{<xliff:g id="COUNT_1">^1</xliff:g> elementer}}"</string>
@@ -160,4 +169,8 @@
<string name="dialog_error_message" msgid="5120432204743681606">"Prøv igen senere. Dine billeder bliver tilgængelige, så snart problemet er løst."</string>
<string name="dialog_error_title" msgid="636349284077820636">"Nogle billeder kan ikke indlæses"</string>
<string name="dialog_button_text" msgid="351366485240852280">"OK"</string>
+ <!-- no translation found for permlab_accessMediaOwnerPackageName (3849443148060165651) -->
+ <skip />
+ <!-- no translation found for permdesc_accessMediaOwnerPackageName (7381563109363105371) -->
+ <skip />
</resources>
diff --git a/res/values-de/strings.xml b/res/values-de/strings.xml
index 658e3f0..acb18c4 100644
--- a/res/values-de/strings.xml
+++ b/res/values-de/strings.xml
@@ -66,11 +66,20 @@
<string name="picker_preview" msgid="6257414886055861039">"Vorschau"</string>
<string name="picker_work_profile" msgid="2083221066869141576">"Zum Arbeitsprofil wechseln"</string>
<string name="picker_personal_profile" msgid="639484258397758406">"Zum privaten Profil wechseln"</string>
+ <string name="picker_personal_profile_label" msgid="6189198163209597344">"Privat"</string>
<string name="picker_profile_admin_title" msgid="4172022376418293777">"Vom Administrator blockiert"</string>
<string name="picker_profile_admin_msg_from_personal" msgid="1941639895084555723">"Der Zugriff auf berufliche Daten von einer privaten App ist nicht zulässig"</string>
<string name="picker_profile_admin_msg_from_work" msgid="8048524337462790110">"Der Zugriff auf private Daten von einer geschäftlichen App ist nicht zulässig"</string>
+ <!-- no translation found for picker_profile_admin_msg (4060112887923255178) -->
+ <skip />
+ <!-- no translation found for picker_profile_switch_message (1133817927412489487) -->
+ <skip />
<string name="picker_profile_work_paused_title" msgid="382212880704235925">"Geschäftliche Apps sind pausiert"</string>
+ <!-- no translation found for picker_profile_paused_title (2079739512895529028) -->
+ <skip />
<string name="picker_profile_work_paused_msg" msgid="6321552322125246726">"Wenn du geschäftliche Fotos aufrufen möchtest, aktiviere zuerst deine geschäftlichen Apps und versuche es dann noch einmal"</string>
+ <!-- no translation found for picker_profile_paused_msg (1215076898583993782) -->
+ <skip />
<string name="picker_privacy_message" msgid="9132700451027116817">"Diese App kann nur auf die Fotos zugreifen, die du auswählst"</string>
<string name="picker_header_permissions" msgid="675872774407768495">"Wähle aus, auf welche Fotos und Videos diese App zugreifen darf"</string>
<string name="picker_album_item_count" msgid="4420723302534177596">"{count,plural, =1{<xliff:g id="COUNT_0">^1</xliff:g> Element}other{<xliff:g id="COUNT_1">^1</xliff:g> Elemente}}"</string>
@@ -160,4 +169,8 @@
<string name="dialog_error_message" msgid="5120432204743681606">"Versuch es später noch einmal. Deine Fotos sind verfügbar, sobald das Problem gelöst wurde."</string>
<string name="dialog_error_title" msgid="636349284077820636">"Einige Fotos konnten nicht geladen werden"</string>
<string name="dialog_button_text" msgid="351366485240852280">"Ok"</string>
+ <!-- no translation found for permlab_accessMediaOwnerPackageName (3849443148060165651) -->
+ <skip />
+ <!-- no translation found for permdesc_accessMediaOwnerPackageName (7381563109363105371) -->
+ <skip />
</resources>
diff --git a/res/values-el/strings.xml b/res/values-el/strings.xml
index 6957172..93ec4ae 100644
--- a/res/values-el/strings.xml
+++ b/res/values-el/strings.xml
@@ -66,11 +66,20 @@
<string name="picker_preview" msgid="6257414886055861039">"Προεπισκόπηση"</string>
<string name="picker_work_profile" msgid="2083221066869141576">"Μετάβαση σε προφίλ εργασίας"</string>
<string name="picker_personal_profile" msgid="639484258397758406">"Μετάβαση σε προσωπικό προφίλ"</string>
+ <string name="picker_personal_profile_label" msgid="6189198163209597344">"Προσωπικό"</string>
<string name="picker_profile_admin_title" msgid="4172022376418293777">"Αποκλείστηκε από τον διαχειριστή σας"</string>
<string name="picker_profile_admin_msg_from_personal" msgid="1941639895084555723">"Δεν επιτρέπεται η πρόσβαση στα δεδομένα εργασίας από μια προσωπική εφαρμογή."</string>
<string name="picker_profile_admin_msg_from_work" msgid="8048524337462790110">"Δεν επιτρέπεται η πρόσβαση στα προσωπικά δεδομένα από μια εφαρμογή εργασιών."</string>
+ <!-- no translation found for picker_profile_admin_msg (4060112887923255178) -->
+ <skip />
+ <!-- no translation found for picker_profile_switch_message (1133817927412489487) -->
+ <skip />
<string name="picker_profile_work_paused_title" msgid="382212880704235925">"Οι εφαρμογές εργασίας τέθηκαν σε παύση"</string>
+ <!-- no translation found for picker_profile_paused_title (2079739512895529028) -->
+ <skip />
<string name="picker_profile_work_paused_msg" msgid="6321552322125246726">"Για να ανοίξετε φωτογραφίες εργασίας, ενεργοποιήστε τις εφαρμογές εργασίας και έπειτα δοκιμάστε ξανά."</string>
+ <!-- no translation found for picker_profile_paused_msg (1215076898583993782) -->
+ <skip />
<string name="picker_privacy_message" msgid="9132700451027116817">"Αυτή η εφαρμογή μπορεί να αποκτήσει πρόσβαση μόνο στις φωτογρ. που επιλέγετε."</string>
<string name="picker_header_permissions" msgid="675872774407768495">"Επιλέξτε φωτογραφίες και βίντεο στις οποίες επιτρέπετε να έχει πρόσβαση η εφαρμογή"</string>
<string name="picker_album_item_count" msgid="4420723302534177596">"{count,plural, =1{<xliff:g id="COUNT_0">^1</xliff:g> στοιχείο}other{<xliff:g id="COUNT_1">^1</xliff:g> στοιχεία}}"</string>
@@ -160,4 +169,8 @@
<string name="dialog_error_message" msgid="5120432204743681606">"Δοκιμάστε ξανά αργότερα. Οι φωτογραφίες σας θα καταστούν διαθέσιμες μόλις επιλυθεί το πρόβλημα."</string>
<string name="dialog_error_title" msgid="636349284077820636">"Δεν είναι δυνατή η φόρτωση ορισμένων φωτογραφιών"</string>
<string name="dialog_button_text" msgid="351366485240852280">"Το κατάλαβα"</string>
+ <!-- no translation found for permlab_accessMediaOwnerPackageName (3849443148060165651) -->
+ <skip />
+ <!-- no translation found for permdesc_accessMediaOwnerPackageName (7381563109363105371) -->
+ <skip />
</resources>
diff --git a/res/values-en-rAU/strings.xml b/res/values-en-rAU/strings.xml
index afa1aa4..6845efe 100644
--- a/res/values-en-rAU/strings.xml
+++ b/res/values-en-rAU/strings.xml
@@ -66,11 +66,20 @@
<string name="picker_preview" msgid="6257414886055861039">"Preview"</string>
<string name="picker_work_profile" msgid="2083221066869141576">"Switch to work"</string>
<string name="picker_personal_profile" msgid="639484258397758406">"Switch to personal"</string>
+ <string name="picker_personal_profile_label" msgid="6189198163209597344">"Personal"</string>
<string name="picker_profile_admin_title" msgid="4172022376418293777">"Blocked by your admin"</string>
<string name="picker_profile_admin_msg_from_personal" msgid="1941639895084555723">"Accessing work data from a personal app is not permitted"</string>
<string name="picker_profile_admin_msg_from_work" msgid="8048524337462790110">"Accessing personal data from a work app is not permitted"</string>
+ <!-- no translation found for picker_profile_admin_msg (4060112887923255178) -->
+ <skip />
+ <!-- no translation found for picker_profile_switch_message (1133817927412489487) -->
+ <skip />
<string name="picker_profile_work_paused_title" msgid="382212880704235925">"Work apps are paused"</string>
+ <!-- no translation found for picker_profile_paused_title (2079739512895529028) -->
+ <skip />
<string name="picker_profile_work_paused_msg" msgid="6321552322125246726">"To open work photos, turn on your work apps and then try again"</string>
+ <!-- no translation found for picker_profile_paused_msg (1215076898583993782) -->
+ <skip />
<string name="picker_privacy_message" msgid="9132700451027116817">"This app can only access the photos that you select"</string>
<string name="picker_header_permissions" msgid="675872774407768495">"Select photos and videos that you allow this app to access"</string>
<string name="picker_album_item_count" msgid="4420723302534177596">"{count,plural, =1{<xliff:g id="COUNT_0">^1</xliff:g> item}other{<xliff:g id="COUNT_1">^1</xliff:g> items}}"</string>
@@ -160,4 +169,8 @@
<string name="dialog_error_message" msgid="5120432204743681606">"Please try again later. Your photos will be available once the issue is resolved."</string>
<string name="dialog_error_title" msgid="636349284077820636">"Can\'t load some photos"</string>
<string name="dialog_button_text" msgid="351366485240852280">"Got it"</string>
+ <!-- no translation found for permlab_accessMediaOwnerPackageName (3849443148060165651) -->
+ <skip />
+ <!-- no translation found for permdesc_accessMediaOwnerPackageName (7381563109363105371) -->
+ <skip />
</resources>
diff --git a/res/values-en-rCA/strings.xml b/res/values-en-rCA/strings.xml
index 4470b43..bec49af 100644
--- a/res/values-en-rCA/strings.xml
+++ b/res/values-en-rCA/strings.xml
@@ -66,11 +66,16 @@
<string name="picker_preview" msgid="6257414886055861039">"Preview"</string>
<string name="picker_work_profile" msgid="2083221066869141576">"Switch to work"</string>
<string name="picker_personal_profile" msgid="639484258397758406">"Switch to personal"</string>
+ <string name="picker_personal_profile_label" msgid="6189198163209597344">"Personal"</string>
<string name="picker_profile_admin_title" msgid="4172022376418293777">"Blocked by your admin"</string>
<string name="picker_profile_admin_msg_from_personal" msgid="1941639895084555723">"Accessing work data from a personal app is not permitted"</string>
<string name="picker_profile_admin_msg_from_work" msgid="8048524337462790110">"Accessing personal data from a work app is not permitted"</string>
+ <string name="picker_profile_admin_msg" msgid="4060112887923255178">"Accessing <xliff:g id="PROFILE1">%1$s</xliff:g> data from a <xliff:g id="PROFILE2">%2$s</xliff:g> app is not permitted"</string>
+ <string name="picker_profile_switch_message" msgid="1133817927412489487">"Switch to <xliff:g id="PROFILE">%s</xliff:g>"</string>
<string name="picker_profile_work_paused_title" msgid="382212880704235925">"Work apps are paused"</string>
+ <string name="picker_profile_paused_title" msgid="2079739512895529028">"<xliff:g id="PROFILE">%s</xliff:g> apps are paused"</string>
<string name="picker_profile_work_paused_msg" msgid="6321552322125246726">"To open work photos, turn on your work apps then try again"</string>
+ <string name="picker_profile_paused_msg" msgid="1215076898583993782">"To open <xliff:g id="PROFILE1">%1$s</xliff:g> photos, turn on your <xliff:g id="PROFILE2">%2$s</xliff:g> apps then try again"</string>
<string name="picker_privacy_message" msgid="9132700451027116817">"This app can only access the photos you select"</string>
<string name="picker_header_permissions" msgid="675872774407768495">"Select photos and videos you allow this app to access"</string>
<string name="picker_album_item_count" msgid="4420723302534177596">"{count,plural, =1{<xliff:g id="COUNT_0">^1</xliff:g> item}other{<xliff:g id="COUNT_1">^1</xliff:g> items}}"</string>
@@ -160,4 +165,6 @@
<string name="dialog_error_message" msgid="5120432204743681606">"Try again later. Your photos will be available once the issue is resolved."</string>
<string name="dialog_error_title" msgid="636349284077820636">"Can\'t load some Photos"</string>
<string name="dialog_button_text" msgid="351366485240852280">"Got it"</string>
+ <string name="permlab_accessMediaOwnerPackageName" msgid="3849443148060165651">"Access media owner package name"</string>
+ <string name="permdesc_accessMediaOwnerPackageName" msgid="7381563109363105371">"Allows the app to access owner package names of all accessible media files."</string>
</resources>
diff --git a/res/values-en-rGB/strings.xml b/res/values-en-rGB/strings.xml
index afa1aa4..6845efe 100644
--- a/res/values-en-rGB/strings.xml
+++ b/res/values-en-rGB/strings.xml
@@ -66,11 +66,20 @@
<string name="picker_preview" msgid="6257414886055861039">"Preview"</string>
<string name="picker_work_profile" msgid="2083221066869141576">"Switch to work"</string>
<string name="picker_personal_profile" msgid="639484258397758406">"Switch to personal"</string>
+ <string name="picker_personal_profile_label" msgid="6189198163209597344">"Personal"</string>
<string name="picker_profile_admin_title" msgid="4172022376418293777">"Blocked by your admin"</string>
<string name="picker_profile_admin_msg_from_personal" msgid="1941639895084555723">"Accessing work data from a personal app is not permitted"</string>
<string name="picker_profile_admin_msg_from_work" msgid="8048524337462790110">"Accessing personal data from a work app is not permitted"</string>
+ <!-- no translation found for picker_profile_admin_msg (4060112887923255178) -->
+ <skip />
+ <!-- no translation found for picker_profile_switch_message (1133817927412489487) -->
+ <skip />
<string name="picker_profile_work_paused_title" msgid="382212880704235925">"Work apps are paused"</string>
+ <!-- no translation found for picker_profile_paused_title (2079739512895529028) -->
+ <skip />
<string name="picker_profile_work_paused_msg" msgid="6321552322125246726">"To open work photos, turn on your work apps and then try again"</string>
+ <!-- no translation found for picker_profile_paused_msg (1215076898583993782) -->
+ <skip />
<string name="picker_privacy_message" msgid="9132700451027116817">"This app can only access the photos that you select"</string>
<string name="picker_header_permissions" msgid="675872774407768495">"Select photos and videos that you allow this app to access"</string>
<string name="picker_album_item_count" msgid="4420723302534177596">"{count,plural, =1{<xliff:g id="COUNT_0">^1</xliff:g> item}other{<xliff:g id="COUNT_1">^1</xliff:g> items}}"</string>
@@ -160,4 +169,8 @@
<string name="dialog_error_message" msgid="5120432204743681606">"Please try again later. Your photos will be available once the issue is resolved."</string>
<string name="dialog_error_title" msgid="636349284077820636">"Can\'t load some photos"</string>
<string name="dialog_button_text" msgid="351366485240852280">"Got it"</string>
+ <!-- no translation found for permlab_accessMediaOwnerPackageName (3849443148060165651) -->
+ <skip />
+ <!-- no translation found for permdesc_accessMediaOwnerPackageName (7381563109363105371) -->
+ <skip />
</resources>
diff --git a/res/values-en-rIN/strings.xml b/res/values-en-rIN/strings.xml
index afa1aa4..6845efe 100644
--- a/res/values-en-rIN/strings.xml
+++ b/res/values-en-rIN/strings.xml
@@ -66,11 +66,20 @@
<string name="picker_preview" msgid="6257414886055861039">"Preview"</string>
<string name="picker_work_profile" msgid="2083221066869141576">"Switch to work"</string>
<string name="picker_personal_profile" msgid="639484258397758406">"Switch to personal"</string>
+ <string name="picker_personal_profile_label" msgid="6189198163209597344">"Personal"</string>
<string name="picker_profile_admin_title" msgid="4172022376418293777">"Blocked by your admin"</string>
<string name="picker_profile_admin_msg_from_personal" msgid="1941639895084555723">"Accessing work data from a personal app is not permitted"</string>
<string name="picker_profile_admin_msg_from_work" msgid="8048524337462790110">"Accessing personal data from a work app is not permitted"</string>
+ <!-- no translation found for picker_profile_admin_msg (4060112887923255178) -->
+ <skip />
+ <!-- no translation found for picker_profile_switch_message (1133817927412489487) -->
+ <skip />
<string name="picker_profile_work_paused_title" msgid="382212880704235925">"Work apps are paused"</string>
+ <!-- no translation found for picker_profile_paused_title (2079739512895529028) -->
+ <skip />
<string name="picker_profile_work_paused_msg" msgid="6321552322125246726">"To open work photos, turn on your work apps and then try again"</string>
+ <!-- no translation found for picker_profile_paused_msg (1215076898583993782) -->
+ <skip />
<string name="picker_privacy_message" msgid="9132700451027116817">"This app can only access the photos that you select"</string>
<string name="picker_header_permissions" msgid="675872774407768495">"Select photos and videos that you allow this app to access"</string>
<string name="picker_album_item_count" msgid="4420723302534177596">"{count,plural, =1{<xliff:g id="COUNT_0">^1</xliff:g> item}other{<xliff:g id="COUNT_1">^1</xliff:g> items}}"</string>
@@ -160,4 +169,8 @@
<string name="dialog_error_message" msgid="5120432204743681606">"Please try again later. Your photos will be available once the issue is resolved."</string>
<string name="dialog_error_title" msgid="636349284077820636">"Can\'t load some photos"</string>
<string name="dialog_button_text" msgid="351366485240852280">"Got it"</string>
+ <!-- no translation found for permlab_accessMediaOwnerPackageName (3849443148060165651) -->
+ <skip />
+ <!-- no translation found for permdesc_accessMediaOwnerPackageName (7381563109363105371) -->
+ <skip />
</resources>
diff --git a/res/values-en-rXC/strings.xml b/res/values-en-rXC/strings.xml
index 100a978..b8e51a8 100644
--- a/res/values-en-rXC/strings.xml
+++ b/res/values-en-rXC/strings.xml
@@ -66,11 +66,16 @@
<string name="picker_preview" msgid="6257414886055861039">"Preview"</string>
<string name="picker_work_profile" msgid="2083221066869141576">"Switch to work"</string>
<string name="picker_personal_profile" msgid="639484258397758406">"Switch to personal"</string>
+ <string name="picker_personal_profile_label" msgid="6189198163209597344">"Personal"</string>
<string name="picker_profile_admin_title" msgid="4172022376418293777">"Blocked by your admin"</string>
<string name="picker_profile_admin_msg_from_personal" msgid="1941639895084555723">"Accessing work data from a personal app is not permitted"</string>
<string name="picker_profile_admin_msg_from_work" msgid="8048524337462790110">"Accessing personal data from a work app is not permitted"</string>
+ <string name="picker_profile_admin_msg" msgid="4060112887923255178">"Accessing <xliff:g id="PROFILE1">%1$s</xliff:g> data from a <xliff:g id="PROFILE2">%2$s</xliff:g> app is not permitted"</string>
+ <string name="picker_profile_switch_message" msgid="1133817927412489487">"Switch to <xliff:g id="PROFILE">%s</xliff:g>"</string>
<string name="picker_profile_work_paused_title" msgid="382212880704235925">"Work apps are paused"</string>
+ <string name="picker_profile_paused_title" msgid="2079739512895529028">"<xliff:g id="PROFILE">%s</xliff:g> apps are paused"</string>
<string name="picker_profile_work_paused_msg" msgid="6321552322125246726">"To open work photos, turn on your work apps then try again"</string>
+ <string name="picker_profile_paused_msg" msgid="1215076898583993782">"To open <xliff:g id="PROFILE1">%1$s</xliff:g> photos, turn on your <xliff:g id="PROFILE2">%2$s</xliff:g> apps then try again"</string>
<string name="picker_privacy_message" msgid="9132700451027116817">"This app can only access the photos you select"</string>
<string name="picker_header_permissions" msgid="675872774407768495">"Select photos and videos you allow this app to access"</string>
<string name="picker_album_item_count" msgid="4420723302534177596">"{count,plural, =1{<xliff:g id="COUNT_0">^1</xliff:g> item}other{<xliff:g id="COUNT_1">^1</xliff:g> items}}"</string>
@@ -160,4 +165,6 @@
<string name="dialog_error_message" msgid="5120432204743681606">"Try again later. Your photos will be available once the issue is resolved."</string>
<string name="dialog_error_title" msgid="636349284077820636">"Can\'t load some Photos"</string>
<string name="dialog_button_text" msgid="351366485240852280">"Got it"</string>
+ <string name="permlab_accessMediaOwnerPackageName" msgid="3849443148060165651">"Access media owner package name"</string>
+ <string name="permdesc_accessMediaOwnerPackageName" msgid="7381563109363105371">"Allows the app to access owner package names of all accessible media files."</string>
</resources>
diff --git a/res/values-es-rUS/strings.xml b/res/values-es-rUS/strings.xml
index 03771b7..7736844 100644
--- a/res/values-es-rUS/strings.xml
+++ b/res/values-es-rUS/strings.xml
@@ -66,11 +66,20 @@
<string name="picker_preview" msgid="6257414886055861039">"Vista previa"</string>
<string name="picker_work_profile" msgid="2083221066869141576">"Cambiar al perfil de trabajo"</string>
<string name="picker_personal_profile" msgid="639484258397758406">"Cambiar al perfil personal"</string>
+ <string name="picker_personal_profile_label" msgid="6189198163209597344">"Personal"</string>
<string name="picker_profile_admin_title" msgid="4172022376418293777">"Bloqueado por tu administrador"</string>
<string name="picker_profile_admin_msg_from_personal" msgid="1941639895084555723">"No se permite el acceso a datos de trabajo desde una app personal"</string>
<string name="picker_profile_admin_msg_from_work" msgid="8048524337462790110">"No se permite el acceso a datos personales desde una app de trabajo"</string>
+ <!-- no translation found for picker_profile_admin_msg (4060112887923255178) -->
+ <skip />
+ <!-- no translation found for picker_profile_switch_message (1133817927412489487) -->
+ <skip />
<string name="picker_profile_work_paused_title" msgid="382212880704235925">"Se pausaron las apps de trabajo"</string>
+ <!-- no translation found for picker_profile_paused_title (2079739512895529028) -->
+ <skip />
<string name="picker_profile_work_paused_msg" msgid="6321552322125246726">"Para abrir fotos de tu perfil de trabajo, activa tus apps de trabajo y vuelve a intentarlo"</string>
+ <!-- no translation found for picker_profile_paused_msg (1215076898583993782) -->
+ <skip />
<string name="picker_privacy_message" msgid="9132700451027116817">"Esta app solo puede acceder a las fotos que selecciones"</string>
<string name="picker_header_permissions" msgid="675872774407768495">"Selecciona las fotos y los vídeos a los que puede acceder esta app"</string>
<string name="picker_album_item_count" msgid="4420723302534177596">"{count,plural, =1{<xliff:g id="COUNT_0">^1</xliff:g> elemento}many{<xliff:g id="COUNT_1">^1</xliff:g> elementos}other{<xliff:g id="COUNT_1">^1</xliff:g> elementos}}"</string>
@@ -160,4 +169,8 @@
<string name="dialog_error_message" msgid="5120432204743681606">"Vuelve a intentarlo más tarde. Tus fotos estarán disponibles una vez que se resuelva el problema."</string>
<string name="dialog_error_title" msgid="636349284077820636">"Se produjo un error durante la carga de algunas fotos"</string>
<string name="dialog_button_text" msgid="351366485240852280">"Entendido"</string>
+ <!-- no translation found for permlab_accessMediaOwnerPackageName (3849443148060165651) -->
+ <skip />
+ <!-- no translation found for permdesc_accessMediaOwnerPackageName (7381563109363105371) -->
+ <skip />
</resources>
diff --git a/res/values-es/strings.xml b/res/values-es/strings.xml
index 532b10b..9d20278 100644
--- a/res/values-es/strings.xml
+++ b/res/values-es/strings.xml
@@ -66,11 +66,20 @@
<string name="picker_preview" msgid="6257414886055861039">"Vista previa"</string>
<string name="picker_work_profile" msgid="2083221066869141576">"Cambiar a perfil de trabajo"</string>
<string name="picker_personal_profile" msgid="639484258397758406">"Cambiar a perfil personal"</string>
+ <string name="picker_personal_profile_label" msgid="6189198163209597344">"Personal"</string>
<string name="picker_profile_admin_title" msgid="4172022376418293777">"Bloqueado por tu administrador"</string>
<string name="picker_profile_admin_msg_from_personal" msgid="1941639895084555723">"No se puede acceder a datos de trabajo desde una aplicación personal"</string>
<string name="picker_profile_admin_msg_from_work" msgid="8048524337462790110">"No se puede acceder a datos personales desde una aplicación de trabajo"</string>
+ <!-- no translation found for picker_profile_admin_msg (4060112887923255178) -->
+ <skip />
+ <!-- no translation found for picker_profile_switch_message (1133817927412489487) -->
+ <skip />
<string name="picker_profile_work_paused_title" msgid="382212880704235925">"Las aplicaciones de trabajo están en pausa"</string>
+ <!-- no translation found for picker_profile_paused_title (2079739512895529028) -->
+ <skip />
<string name="picker_profile_work_paused_msg" msgid="6321552322125246726">"Para abrir fotos del trabajo, activa tus aplicaciones de trabajo e inténtalo de nuevo"</string>
+ <!-- no translation found for picker_profile_paused_msg (1215076898583993782) -->
+ <skip />
<string name="picker_privacy_message" msgid="9132700451027116817">"Esta aplicación solo puede acceder a las fotos que selecciones"</string>
<string name="picker_header_permissions" msgid="675872774407768495">"Selecciona las fotos y los vídeos a los que permites que acceda esta aplicación"</string>
<string name="picker_album_item_count" msgid="4420723302534177596">"{count,plural, =1{<xliff:g id="COUNT_0">^1</xliff:g> elemento}many{<xliff:g id="COUNT_1">^1</xliff:g> elementos}other{<xliff:g id="COUNT_1">^1</xliff:g> elementos}}"</string>
@@ -160,4 +169,8 @@
<string name="dialog_error_message" msgid="5120432204743681606">"Inténtalo de nuevo más tarde. Tus fotos estarán disponibles cuando se resuelva el problema."</string>
<string name="dialog_error_title" msgid="636349284077820636">"No se pueden cargar algunas fotos"</string>
<string name="dialog_button_text" msgid="351366485240852280">"Entendido"</string>
+ <!-- no translation found for permlab_accessMediaOwnerPackageName (3849443148060165651) -->
+ <skip />
+ <!-- no translation found for permdesc_accessMediaOwnerPackageName (7381563109363105371) -->
+ <skip />
</resources>
diff --git a/res/values-et/strings.xml b/res/values-et/strings.xml
index 51ff309..d7b3960 100644
--- a/res/values-et/strings.xml
+++ b/res/values-et/strings.xml
@@ -66,11 +66,20 @@
<string name="picker_preview" msgid="6257414886055861039">"Eelvaade"</string>
<string name="picker_work_profile" msgid="2083221066869141576">"Lülituge tööprofiilile"</string>
<string name="picker_personal_profile" msgid="639484258397758406">"Lülituge isiklikule profiilile"</string>
+ <string name="picker_personal_profile_label" msgid="6189198163209597344">"Isiklik"</string>
<string name="picker_profile_admin_title" msgid="4172022376418293777">"Blokeeris teie administraator"</string>
<string name="picker_profile_admin_msg_from_personal" msgid="1941639895084555723">"Juurdepääs tööandmetele isikliku rakenduse kaudu pole lubatud."</string>
<string name="picker_profile_admin_msg_from_work" msgid="8048524337462790110">"Juurdepääs isiklikele andmetele töörakenduse kaudu pole lubatud"</string>
+ <!-- no translation found for picker_profile_admin_msg (4060112887923255178) -->
+ <skip />
+ <!-- no translation found for picker_profile_switch_message (1133817927412489487) -->
+ <skip />
<string name="picker_profile_work_paused_title" msgid="382212880704235925">"Töörakendused on peatatud"</string>
+ <!-- no translation found for picker_profile_paused_title (2079739512895529028) -->
+ <skip />
<string name="picker_profile_work_paused_msg" msgid="6321552322125246726">"Tööfotode avamiseks lülitage töörakendused sisse ja proovige uuesti"</string>
+ <!-- no translation found for picker_profile_paused_msg (1215076898583993782) -->
+ <skip />
<string name="picker_privacy_message" msgid="9132700451027116817">"See rakendus pääseb juurde vaid teie valitud fotodele"</string>
<string name="picker_header_permissions" msgid="675872774407768495">"Valige fotod ja videod, millele lubate sellel rakendusel juurde pääseda"</string>
<string name="picker_album_item_count" msgid="4420723302534177596">"{count,plural, =1{<xliff:g id="COUNT_0">^1</xliff:g> üksus}other{<xliff:g id="COUNT_1">^1</xliff:g> üksust}}"</string>
@@ -160,4 +169,8 @@
<string name="dialog_error_message" msgid="5120432204743681606">"Proovige hiljem uuesti. Teie fotod on saadaval pärast probleemi lahendamist."</string>
<string name="dialog_error_title" msgid="636349284077820636">"Mõnda fotot ei saa laadida"</string>
<string name="dialog_button_text" msgid="351366485240852280">"Selge"</string>
+ <!-- no translation found for permlab_accessMediaOwnerPackageName (3849443148060165651) -->
+ <skip />
+ <!-- no translation found for permdesc_accessMediaOwnerPackageName (7381563109363105371) -->
+ <skip />
</resources>
diff --git a/res/values-eu/strings.xml b/res/values-eu/strings.xml
index be1db85..aa8807f 100644
--- a/res/values-eu/strings.xml
+++ b/res/values-eu/strings.xml
@@ -66,11 +66,20 @@
<string name="picker_preview" msgid="6257414886055861039">"Aurrebista"</string>
<string name="picker_work_profile" msgid="2083221066869141576">"Aldatu laneko profilera"</string>
<string name="picker_personal_profile" msgid="639484258397758406">"Aldatu profil pertsonalera"</string>
+ <string name="picker_personal_profile_label" msgid="6189198163209597344">"Pertsonala"</string>
<string name="picker_profile_admin_title" msgid="4172022376418293777">"Administratzaileak blokeatu du"</string>
<string name="picker_profile_admin_msg_from_personal" msgid="1941639895084555723">"Laneko datuak ezin dira aplikazio pertsonalen bidez atzitu"</string>
<string name="picker_profile_admin_msg_from_work" msgid="8048524337462790110">"Datu pertsonalak ezin dira laneko aplikazioen bidez atzitu"</string>
+ <!-- no translation found for picker_profile_admin_msg (4060112887923255178) -->
+ <skip />
+ <!-- no translation found for picker_profile_switch_message (1133817927412489487) -->
+ <skip />
<string name="picker_profile_work_paused_title" msgid="382212880704235925">"Pausatuta daude laneko aplikazioak"</string>
+ <!-- no translation found for picker_profile_paused_title (2079739512895529028) -->
+ <skip />
<string name="picker_profile_work_paused_msg" msgid="6321552322125246726">"Laneko argazkiak irekitzeko, aktibatu laneko aplikazioak eta saiatu berriro"</string>
+ <!-- no translation found for picker_profile_paused_msg (1215076898583993782) -->
+ <skip />
<string name="picker_privacy_message" msgid="9132700451027116817">"Zuk hautatutako argazkiak bakarrik atzi ditzake aplikazio honek"</string>
<string name="picker_header_permissions" msgid="675872774407768495">"Hautatu aplikazioak erabil ditzakeen argazki eta bideoak"</string>
<string name="picker_album_item_count" msgid="4420723302534177596">"{count,plural, =1{<xliff:g id="COUNT_0">^1</xliff:g> elementu}other{<xliff:g id="COUNT_1">^1</xliff:g> elementu}}"</string>
@@ -160,4 +169,8 @@
<string name="dialog_error_message" msgid="5120432204743681606">"Saiatu berriro geroago. Arazoa konpondu ondoren egongo dira erabilgarri argazkiak."</string>
<string name="dialog_error_title" msgid="636349284077820636">"Ezin dira kargatu argazki batzuk"</string>
<string name="dialog_button_text" msgid="351366485240852280">"Ados"</string>
+ <!-- no translation found for permlab_accessMediaOwnerPackageName (3849443148060165651) -->
+ <skip />
+ <!-- no translation found for permdesc_accessMediaOwnerPackageName (7381563109363105371) -->
+ <skip />
</resources>
diff --git a/res/values-fa/strings.xml b/res/values-fa/strings.xml
index 5e515e0..10c6399 100644
--- a/res/values-fa/strings.xml
+++ b/res/values-fa/strings.xml
@@ -66,11 +66,20 @@
<string name="picker_preview" msgid="6257414886055861039">"پیشنما"</string>
<string name="picker_work_profile" msgid="2083221066869141576">"رفتن به نمایه کاری"</string>
<string name="picker_personal_profile" msgid="639484258397758406">"رفتن به نمایه شخصی"</string>
+ <string name="picker_personal_profile_label" msgid="6189198163209597344">"شخصی"</string>
<string name="picker_profile_admin_title" msgid="4172022376418293777">"سرپرست آن را مسدود کرده است"</string>
<string name="picker_profile_admin_msg_from_personal" msgid="1941639895084555723">"دسترسی به دادههای کاری ازطریق برنامه شخصی مجاز نیست"</string>
<string name="picker_profile_admin_msg_from_work" msgid="8048524337462790110">"دسترسی به دادههای شخصی ازطریق برنامه کاری مجاز نیست"</string>
+ <!-- no translation found for picker_profile_admin_msg (4060112887923255178) -->
+ <skip />
+ <!-- no translation found for picker_profile_switch_message (1133817927412489487) -->
+ <skip />
<string name="picker_profile_work_paused_title" msgid="382212880704235925">"برنامههای کاری موقتاً متوقف شدهاند."</string>
+ <!-- no translation found for picker_profile_paused_title (2079739512895529028) -->
+ <skip />
<string name="picker_profile_work_paused_msg" msgid="6321552322125246726">"برای باز کردن عکسهای کاری، برنامههای کاری را روشن کنید و سپس دوباره امتحان کنید"</string>
+ <!-- no translation found for picker_profile_paused_msg (1215076898583993782) -->
+ <skip />
<string name="picker_privacy_message" msgid="9132700451027116817">"این برنامه فقط میتواند به عکسهای انتخابی شما دسترسی پیدا کند"</string>
<string name="picker_header_permissions" msgid="675872774407768495">"عکسها و ویدیوهایی را که اجازه میدهید این برنامه به آنها دسترسی داشته باشد انتخاب کنید"</string>
<string name="picker_album_item_count" msgid="4420723302534177596">"{count,plural, =1{<xliff:g id="COUNT_0">^1</xliff:g> مورد}one{<xliff:g id="COUNT_1">^1</xliff:g> مورد}other{<xliff:g id="COUNT_1">^1</xliff:g> مورد}}"</string>
@@ -160,4 +169,8 @@
<string name="dialog_error_message" msgid="5120432204743681606">"بعداً دوباره امتحان کنید. عکسهایتان پساز رفع مشکل دردسترس خواهد بود."</string>
<string name="dialog_error_title" msgid="636349284077820636">"برخیاز عکسها را نمیتوان بار کرد"</string>
<string name="dialog_button_text" msgid="351366485240852280">"متوجهام"</string>
+ <!-- no translation found for permlab_accessMediaOwnerPackageName (3849443148060165651) -->
+ <skip />
+ <!-- no translation found for permdesc_accessMediaOwnerPackageName (7381563109363105371) -->
+ <skip />
</resources>
diff --git a/res/values-fi/strings.xml b/res/values-fi/strings.xml
index bfd4d84..aaf1f5c 100644
--- a/res/values-fi/strings.xml
+++ b/res/values-fi/strings.xml
@@ -66,11 +66,20 @@
<string name="picker_preview" msgid="6257414886055861039">"Esikatselu"</string>
<string name="picker_work_profile" msgid="2083221066869141576">"Siirry työprofiiliin"</string>
<string name="picker_personal_profile" msgid="639484258397758406">"Siirry henkilökohtaiseen profiiliin"</string>
+ <string name="picker_personal_profile_label" msgid="6189198163209597344">"Henkilökohtainen"</string>
<string name="picker_profile_admin_title" msgid="4172022376418293777">"Järjestelmänvalvojasi estämä"</string>
<string name="picker_profile_admin_msg_from_personal" msgid="1941639895084555723">"Työdataan pääsy ei ole sallittu henkilökohtaisen sovelluksen kautta"</string>
<string name="picker_profile_admin_msg_from_work" msgid="8048524337462790110">"Henkilökohtaiseen dataan pääsy ei ole sallittu työsovelluksen kautta"</string>
+ <!-- no translation found for picker_profile_admin_msg (4060112887923255178) -->
+ <skip />
+ <!-- no translation found for picker_profile_switch_message (1133817927412489487) -->
+ <skip />
<string name="picker_profile_work_paused_title" msgid="382212880704235925">"Työsovellukset on keskeytetty"</string>
+ <!-- no translation found for picker_profile_paused_title (2079739512895529028) -->
+ <skip />
<string name="picker_profile_work_paused_msg" msgid="6321552322125246726">"Jos haluat avata työkuvasi, laita työsovellukset päälle ja yritä uudelleen"</string>
+ <!-- no translation found for picker_profile_paused_msg (1215076898583993782) -->
+ <skip />
<string name="picker_privacy_message" msgid="9132700451027116817">"Sovelluksella on pääsy vain valitsemiisi kuviin"</string>
<string name="picker_header_permissions" msgid="675872774407768495">"Valitse kuvat ja videot, joihin sovellus saa pääsyn"</string>
<string name="picker_album_item_count" msgid="4420723302534177596">"{count,plural, =1{<xliff:g id="COUNT_0">^1</xliff:g> kohde}other{<xliff:g id="COUNT_1">^1</xliff:g> kohdetta}}"</string>
@@ -160,4 +169,8 @@
<string name="dialog_error_message" msgid="5120432204743681606">"Yritä myöhemmin uudelleen. Kuvat ovat saatavilla, kun ongelma on korjattu."</string>
<string name="dialog_error_title" msgid="636349284077820636">"Joitain kuvia ei voi ladata"</string>
<string name="dialog_button_text" msgid="351366485240852280">"OK"</string>
+ <!-- no translation found for permlab_accessMediaOwnerPackageName (3849443148060165651) -->
+ <skip />
+ <!-- no translation found for permdesc_accessMediaOwnerPackageName (7381563109363105371) -->
+ <skip />
</resources>
diff --git a/res/values-fr-rCA/strings.xml b/res/values-fr-rCA/strings.xml
index a351916..75376e3 100644
--- a/res/values-fr-rCA/strings.xml
+++ b/res/values-fr-rCA/strings.xml
@@ -66,11 +66,20 @@
<string name="picker_preview" msgid="6257414886055861039">"Aperçu"</string>
<string name="picker_work_profile" msgid="2083221066869141576">"Passez au profil professionnel"</string>
<string name="picker_personal_profile" msgid="639484258397758406">"Passez au profil personnel"</string>
+ <string name="picker_personal_profile_label" msgid="6189198163209597344">"Personnel"</string>
<string name="picker_profile_admin_title" msgid="4172022376418293777">"Bloqué par votre administrateur"</string>
<string name="picker_profile_admin_msg_from_personal" msgid="1941639895084555723">"Il est interdit d\'accéder aux données professionnelles à partir d\'une application personnelle"</string>
<string name="picker_profile_admin_msg_from_work" msgid="8048524337462790110">"Il est interdit d\'accéder aux données personnelles à partir d\'une application professionnelle"</string>
+ <!-- no translation found for picker_profile_admin_msg (4060112887923255178) -->
+ <skip />
+ <!-- no translation found for picker_profile_switch_message (1133817927412489487) -->
+ <skip />
<string name="picker_profile_work_paused_title" msgid="382212880704235925">"Les applications professionnelles sont interrompues"</string>
+ <!-- no translation found for picker_profile_paused_title (2079739512895529028) -->
+ <skip />
<string name="picker_profile_work_paused_msg" msgid="6321552322125246726">"Pour ouvrir des photos professionnelles, activez vos applications professionnelles, puis réessayez"</string>
+ <!-- no translation found for picker_profile_paused_msg (1215076898583993782) -->
+ <skip />
<string name="picker_privacy_message" msgid="9132700451027116817">"Cette application ne peut accéder qu\'aux photos que vous sélectionnez"</string>
<string name="picker_header_permissions" msgid="675872774407768495">"Sélectionnez les photos et les vidéos auxquelles cette application peut accéder"</string>
<string name="picker_album_item_count" msgid="4420723302534177596">"{count,plural, =1{<xliff:g id="COUNT_0">^1</xliff:g> élément}one{<xliff:g id="COUNT_1">^1</xliff:g> élément}many{<xliff:g id="COUNT_1">^1</xliff:g> éléments}other{<xliff:g id="COUNT_1">^1</xliff:g> éléments}}"</string>
@@ -160,4 +169,8 @@
<string name="dialog_error_message" msgid="5120432204743681606">"Réessayez plus tard. Vos photos seront accessibles dès que le problème sera résolu."</string>
<string name="dialog_error_title" msgid="636349284077820636">"Impossible de charger certaines photos"</string>
<string name="dialog_button_text" msgid="351366485240852280">"OK"</string>
+ <!-- no translation found for permlab_accessMediaOwnerPackageName (3849443148060165651) -->
+ <skip />
+ <!-- no translation found for permdesc_accessMediaOwnerPackageName (7381563109363105371) -->
+ <skip />
</resources>
diff --git a/res/values-fr/strings.xml b/res/values-fr/strings.xml
index 6398796..46b3a47 100644
--- a/res/values-fr/strings.xml
+++ b/res/values-fr/strings.xml
@@ -66,11 +66,20 @@
<string name="picker_preview" msgid="6257414886055861039">"Aperçu"</string>
<string name="picker_work_profile" msgid="2083221066869141576">"Passer au profil professionnel"</string>
<string name="picker_personal_profile" msgid="639484258397758406">"Passer au profil personnel"</string>
+ <string name="picker_personal_profile_label" msgid="6189198163209597344">"Personnel"</string>
<string name="picker_profile_admin_title" msgid="4172022376418293777">"Bloqué par votre administrateur"</string>
<string name="picker_profile_admin_msg_from_personal" msgid="1941639895084555723">"Vous n\'êtes pas autorisé à accéder à des données professionnelles depuis une appli personnelle"</string>
<string name="picker_profile_admin_msg_from_work" msgid="8048524337462790110">"Vous n\'êtes pas autorisé à accéder à des données à caractère personnel depuis une appli professionnelle"</string>
+ <!-- no translation found for picker_profile_admin_msg (4060112887923255178) -->
+ <skip />
+ <!-- no translation found for picker_profile_switch_message (1133817927412489487) -->
+ <skip />
<string name="picker_profile_work_paused_title" msgid="382212880704235925">"Applis professionnelles en pause"</string>
+ <!-- no translation found for picker_profile_paused_title (2079739512895529028) -->
+ <skip />
<string name="picker_profile_work_paused_msg" msgid="6321552322125246726">"Pour ouvrir des photos professionnelles, activez vos applis professionnelles, puis réessayez"</string>
+ <!-- no translation found for picker_profile_paused_msg (1215076898583993782) -->
+ <skip />
<string name="picker_privacy_message" msgid="9132700451027116817">"Cette appli peut uniquement accéder aux photos que vous sélectionnez"</string>
<string name="picker_header_permissions" msgid="675872774407768495">"Sélectionnez les photos et vidéos auxquelles cette appli peut accéder"</string>
<string name="picker_album_item_count" msgid="4420723302534177596">"{count,plural, =1{<xliff:g id="COUNT_0">^1</xliff:g> élément}one{<xliff:g id="COUNT_1">^1</xliff:g> élément}many{<xliff:g id="COUNT_1">^1</xliff:g> éléments}other{<xliff:g id="COUNT_1">^1</xliff:g> éléments}}"</string>
@@ -160,4 +169,6 @@
<string name="dialog_error_message" msgid="5120432204743681606">"Réessayez plus tard. Vos photos seront disponibles une fois le problème résolu."</string>
<string name="dialog_error_title" msgid="636349284077820636">"Impossible de charger certaines photos"</string>
<string name="dialog_button_text" msgid="351366485240852280">"OK"</string>
+ <string name="permlab_accessMediaOwnerPackageName" msgid="3849443148060165651">"Accéder au nom de package du propriétaire des fichiers multimédias"</string>
+ <string name="permdesc_accessMediaOwnerPackageName" msgid="7381563109363105371">"Autorisez l\'application à accéder aux noms de package du propriétaire de l\'ensemble des fichiers multimédias accessibles."</string>
</resources>
diff --git a/res/values-gl/strings.xml b/res/values-gl/strings.xml
index b0fda91..4245b41 100644
--- a/res/values-gl/strings.xml
+++ b/res/values-gl/strings.xml
@@ -66,11 +66,20 @@
<string name="picker_preview" msgid="6257414886055861039">"Vista previa"</string>
<string name="picker_work_profile" msgid="2083221066869141576">"Cambiar ao perfil de traballo"</string>
<string name="picker_personal_profile" msgid="639484258397758406">"Cambiar ao perfil persoal"</string>
+ <string name="picker_personal_profile_label" msgid="6189198163209597344">"Persoal"</string>
<string name="picker_profile_admin_title" msgid="4172022376418293777">"Bloqueado polo administrador"</string>
<string name="picker_profile_admin_msg_from_personal" msgid="1941639895084555723">"Non está permitido acceder aos datos do traballo desde unha aplicación persoal"</string>
<string name="picker_profile_admin_msg_from_work" msgid="8048524337462790110">"Non está permitido acceder aos datos persoais desde unha aplicación do traballo"</string>
+ <!-- no translation found for picker_profile_admin_msg (4060112887923255178) -->
+ <skip />
+ <!-- no translation found for picker_profile_switch_message (1133817927412489487) -->
+ <skip />
<string name="picker_profile_work_paused_title" msgid="382212880704235925">"Puxéronse en pausa as aplicacións do traballo"</string>
+ <!-- no translation found for picker_profile_paused_title (2079739512895529028) -->
+ <skip />
<string name="picker_profile_work_paused_msg" msgid="6321552322125246726">"Para abrir fotos do perfil de traballo, activa as aplicacións do traballo e, a continuación, téntao de novo"</string>
+ <!-- no translation found for picker_profile_paused_msg (1215076898583993782) -->
+ <skip />
<string name="picker_privacy_message" msgid="9132700451027116817">"Esta aplicación só pode acceder ás fotos que selecciones"</string>
<string name="picker_header_permissions" msgid="675872774407768495">"Selecciona as fotos e os vídeos aos que permites que esta aplicación acceda"</string>
<string name="picker_album_item_count" msgid="4420723302534177596">"{count,plural, =1{<xliff:g id="COUNT_0">^1</xliff:g> elemento}other{<xliff:g id="COUNT_1">^1</xliff:g> elementos}}"</string>
@@ -160,4 +169,8 @@
<string name="dialog_error_message" msgid="5120432204743681606">"Téntao de novo máis tarde. As túas fotos estarán dispoñibles en canto se resolva o problema."</string>
<string name="dialog_error_title" msgid="636349284077820636">"Non se poden cargar algunhas fotos"</string>
<string name="dialog_button_text" msgid="351366485240852280">"Entendido"</string>
+ <!-- no translation found for permlab_accessMediaOwnerPackageName (3849443148060165651) -->
+ <skip />
+ <!-- no translation found for permdesc_accessMediaOwnerPackageName (7381563109363105371) -->
+ <skip />
</resources>
diff --git a/res/values-gu/strings.xml b/res/values-gu/strings.xml
index b46ad96..6c40546 100644
--- a/res/values-gu/strings.xml
+++ b/res/values-gu/strings.xml
@@ -66,11 +66,20 @@
<string name="picker_preview" msgid="6257414886055861039">"પ્રીવ્યૂ કરો"</string>
<string name="picker_work_profile" msgid="2083221066869141576">"ઑફિસની પ્રોફાઇલ પર સ્વિચ કરો"</string>
<string name="picker_personal_profile" msgid="639484258397758406">"વ્યક્તિગત પ્રોફાઇલ પર સ્વિચ કરો"</string>
+ <string name="picker_personal_profile_label" msgid="6189198163209597344">"વ્યક્તિગત"</string>
<string name="picker_profile_admin_title" msgid="4172022376418293777">"તમારા ઍડમિને સુવિધા બ્લૉક કરી છે"</string>
<string name="picker_profile_admin_msg_from_personal" msgid="1941639895084555723">"વ્યક્તિગત ઍપ પરથી ઑફિસનો ડેટા ઍક્સેસ કરવાની પરવાનગી નથી"</string>
<string name="picker_profile_admin_msg_from_work" msgid="8048524337462790110">"ઑફિસ માટેની ઍપ પરથી વ્યક્તિગત ડેટા ઍક્સેસ કરવાની પરવાનગી નથી"</string>
+ <!-- no translation found for picker_profile_admin_msg (4060112887923255178) -->
+ <skip />
+ <!-- no translation found for picker_profile_switch_message (1133817927412489487) -->
+ <skip />
<string name="picker_profile_work_paused_title" msgid="382212880704235925">"ઑફિસ માટેની ઍપ થોભાવવામાં આવી છે"</string>
+ <!-- no translation found for picker_profile_paused_title (2079739512895529028) -->
+ <skip />
<string name="picker_profile_work_paused_msg" msgid="6321552322125246726">"ઑફિસના ફોટા ખોલવા માટે, તમારી ઑફિસ માટેની ઍપ ખોલો અને પછી ફરી પ્રયાસ કરો"</string>
+ <!-- no translation found for picker_profile_paused_msg (1215076898583993782) -->
+ <skip />
<string name="picker_privacy_message" msgid="9132700451027116817">"આ ઍપ માત્ર તમે પસંદ કરેલા ફોટા જ ઍક્સેસ કરી શકે છે"</string>
<string name="picker_header_permissions" msgid="675872774407768495">"તમે આ ઍપને જે ફોટા અને વીડિયો ઍક્સેસ કરવાની મંજૂરી આપતા હો, તેને પસંદ કરો"</string>
<string name="picker_album_item_count" msgid="4420723302534177596">"{count,plural, =1{<xliff:g id="COUNT_0">^1</xliff:g> આઇટમ}one{<xliff:g id="COUNT_1">^1</xliff:g> આઇટમ}other{<xliff:g id="COUNT_1">^1</xliff:g> આઇટમ}}"</string>
@@ -160,4 +169,8 @@
<string name="dialog_error_message" msgid="5120432204743681606">"થોડા સમય પછી ફરી પ્રયાસ કરો. એકવાર સમસ્યાનું નિરાકરણ થઈ જાય, તે પછી તમારા ફોટા ઉપલબ્ધ થશે."</string>
<string name="dialog_error_title" msgid="636349284077820636">"અમુક ફોટા લોડ કરી શકાતા નથી"</string>
<string name="dialog_button_text" msgid="351366485240852280">"સમજાઈ ગયું"</string>
+ <!-- no translation found for permlab_accessMediaOwnerPackageName (3849443148060165651) -->
+ <skip />
+ <!-- no translation found for permdesc_accessMediaOwnerPackageName (7381563109363105371) -->
+ <skip />
</resources>
diff --git a/res/values-hi/strings.xml b/res/values-hi/strings.xml
index a493629..50c12d2 100644
--- a/res/values-hi/strings.xml
+++ b/res/values-hi/strings.xml
@@ -66,11 +66,20 @@
<string name="picker_preview" msgid="6257414886055861039">"झलक"</string>
<string name="picker_work_profile" msgid="2083221066869141576">"वर्क प्रोफ़ाइल पर जाएं"</string>
<string name="picker_personal_profile" msgid="639484258397758406">"निजी प्रोफ़ाइल पर जाएं"</string>
+ <string name="picker_personal_profile_label" msgid="6189198163209597344">"निजी"</string>
<string name="picker_profile_admin_title" msgid="4172022376418293777">"आपके एडमिन ने रोक लगाई है"</string>
<string name="picker_profile_admin_msg_from_personal" msgid="1941639895084555723">"ऑफ़िस के काम से जुड़े डेटा को निजी ऐप्लिकेशन से ऐक्सेस करने की अनुमति नहीं है"</string>
<string name="picker_profile_admin_msg_from_work" msgid="8048524337462790110">"निजी डेटा को ऑफ़िस के काम से जुड़े ऐप्लिकेशन से ऐक्सेस करने की अनुमति नहीं है"</string>
+ <!-- no translation found for picker_profile_admin_msg (4060112887923255178) -->
+ <skip />
+ <!-- no translation found for picker_profile_switch_message (1133817927412489487) -->
+ <skip />
<string name="picker_profile_work_paused_title" msgid="382212880704235925">"वर्क ऐप्लिकेशन रोक दिए गए हैं"</string>
+ <!-- no translation found for picker_profile_paused_title (2079739512895529028) -->
+ <skip />
<string name="picker_profile_work_paused_msg" msgid="6321552322125246726">"वर्क फ़ोटो देखने के लिए, ऑफ़िस के काम से जुड़े ऐप्लिकेशन चालू करें और दोबारा कोशिश करें"</string>
+ <!-- no translation found for picker_profile_paused_msg (1215076898583993782) -->
+ <skip />
<string name="picker_privacy_message" msgid="9132700451027116817">"इस ऐप्लिकेशन के पास आपकी उन फ़ोटो का ही ऐक्सेस होता है जिन्हें आपने चुना है"</string>
<string name="picker_header_permissions" msgid="675872774407768495">"वे फ़ोटो और वीडियो चुनें जिनका ऐक्सेस इस ऐप्लिकेशन को देना है"</string>
<string name="picker_album_item_count" msgid="4420723302534177596">"{count,plural, =1{<xliff:g id="COUNT_0">^1</xliff:g> आइटम}one{<xliff:g id="COUNT_1">^1</xliff:g> आइटम}other{<xliff:g id="COUNT_1">^1</xliff:g> आइटम}}"</string>
@@ -160,4 +169,8 @@
<string name="dialog_error_message" msgid="5120432204743681606">"कुछ देर बाद कोशिश करें. समस्या हल होते ही आपकी फ़ोटो उपलब्ध हो जाएंगी."</string>
<string name="dialog_error_title" msgid="636349284077820636">"कुछ फ़ोटो लोड नहीं की जा सकीं"</string>
<string name="dialog_button_text" msgid="351366485240852280">"ठीक है"</string>
+ <!-- no translation found for permlab_accessMediaOwnerPackageName (3849443148060165651) -->
+ <skip />
+ <!-- no translation found for permdesc_accessMediaOwnerPackageName (7381563109363105371) -->
+ <skip />
</resources>
diff --git a/res/values-hr/strings.xml b/res/values-hr/strings.xml
index c989254..65304fc 100644
--- a/res/values-hr/strings.xml
+++ b/res/values-hr/strings.xml
@@ -66,11 +66,20 @@
<string name="picker_preview" msgid="6257414886055861039">"Pregled"</string>
<string name="picker_work_profile" msgid="2083221066869141576">"Prijeđite na poslovni"</string>
<string name="picker_personal_profile" msgid="639484258397758406">"Prijeđite na osobni"</string>
+ <string name="picker_personal_profile_label" msgid="6189198163209597344">"Osobno"</string>
<string name="picker_profile_admin_title" msgid="4172022376418293777">"Blokirao vaš administrator"</string>
<string name="picker_profile_admin_msg_from_personal" msgid="1941639895084555723">"Pristupanje poslovnim podacima putem osobne aplikacije nije dopušteno"</string>
<string name="picker_profile_admin_msg_from_work" msgid="8048524337462790110">"Pristupanje osobnim podacima putem poslovne aplikacije nije dopušteno"</string>
+ <!-- no translation found for picker_profile_admin_msg (4060112887923255178) -->
+ <skip />
+ <!-- no translation found for picker_profile_switch_message (1133817927412489487) -->
+ <skip />
<string name="picker_profile_work_paused_title" msgid="382212880704235925">"Poslovne aplikacije su pauzirane"</string>
+ <!-- no translation found for picker_profile_paused_title (2079739512895529028) -->
+ <skip />
<string name="picker_profile_work_paused_msg" msgid="6321552322125246726">"Da biste otvorili fotografije s poslovnog profila, uključite poslovne aplikacije i pokušajte ponovo"</string>
+ <!-- no translation found for picker_profile_paused_msg (1215076898583993782) -->
+ <skip />
<string name="picker_privacy_message" msgid="9132700451027116817">"Ova aplikacija može pristupiti samo fotografijama koje odaberete"</string>
<string name="picker_header_permissions" msgid="675872774407768495">"Odaberite fotografije i videozapise kojima će aplikacija moći pristupiti"</string>
<string name="picker_album_item_count" msgid="4420723302534177596">"{count,plural, =1{<xliff:g id="COUNT_0">^1</xliff:g> stavka}one{<xliff:g id="COUNT_1">^1</xliff:g> stavka}few{<xliff:g id="COUNT_1">^1</xliff:g> stavke}other{<xliff:g id="COUNT_1">^1</xliff:g> stavki}}"</string>
@@ -160,4 +169,8 @@
<string name="dialog_error_message" msgid="5120432204743681606">"Pokušajte ponovo poslije. Vaše fotografije bit će dostupne kad se problem riješi."</string>
<string name="dialog_error_title" msgid="636349284077820636">"Neke fotografije ne mogu se učitati"</string>
<string name="dialog_button_text" msgid="351366485240852280">"Shvaćam"</string>
+ <!-- no translation found for permlab_accessMediaOwnerPackageName (3849443148060165651) -->
+ <skip />
+ <!-- no translation found for permdesc_accessMediaOwnerPackageName (7381563109363105371) -->
+ <skip />
</resources>
diff --git a/res/values-hu/strings.xml b/res/values-hu/strings.xml
index c5d7870..dfa6313 100644
--- a/res/values-hu/strings.xml
+++ b/res/values-hu/strings.xml
@@ -66,11 +66,20 @@
<string name="picker_preview" msgid="6257414886055861039">"Előnézet"</string>
<string name="picker_work_profile" msgid="2083221066869141576">"Átváltás munkaprofilra"</string>
<string name="picker_personal_profile" msgid="639484258397758406">"Átváltás személyes profilra"</string>
+ <string name="picker_personal_profile_label" msgid="6189198163209597344">"Személyes"</string>
<string name="picker_profile_admin_title" msgid="4172022376418293777">"Rendszergazda által letiltva"</string>
<string name="picker_profile_admin_msg_from_personal" msgid="1941639895084555723">"A munkahelyi adatokhoz való hozzáférés személyes alkalmazással nincs engedélyezve"</string>
<string name="picker_profile_admin_msg_from_work" msgid="8048524337462790110">"A személyes adatokhoz való hozzáférés munkahelyi alkalmazással nincs engedélyezve"</string>
+ <!-- no translation found for picker_profile_admin_msg (4060112887923255178) -->
+ <skip />
+ <!-- no translation found for picker_profile_switch_message (1133817927412489487) -->
+ <skip />
<string name="picker_profile_work_paused_title" msgid="382212880704235925">"A munkahelyi alkalmazások szüneteltetve vannak"</string>
+ <!-- no translation found for picker_profile_paused_title (2079739512895529028) -->
+ <skip />
<string name="picker_profile_work_paused_msg" msgid="6321552322125246726">"A munkahelyi fotók megnyitásához kapcsolja be a munkahelyi alkalmazásokat, majd próbálkozzon újra"</string>
+ <!-- no translation found for picker_profile_paused_msg (1215076898583993782) -->
+ <skip />
<string name="picker_privacy_message" msgid="9132700451027116817">"Ez az alkalmazás csak az Ön által kiválasztott fotókhoz férhet hozzá"</string>
<string name="picker_header_permissions" msgid="675872774407768495">"Válassza ki azokat a fotókat és videókat, amelyekhez engedélyezi az alkalmazás számára a hozzáférést"</string>
<string name="picker_album_item_count" msgid="4420723302534177596">"{count,plural, =1{<xliff:g id="COUNT_0">^1</xliff:g> elem}other{<xliff:g id="COUNT_1">^1</xliff:g> elem}}"</string>
@@ -160,4 +169,8 @@
<string name="dialog_error_message" msgid="5120432204743681606">"Próbálkozzon újra később. Fotói hozzáférhetők lesznek a probléma elhárítását követően."</string>
<string name="dialog_error_title" msgid="636349284077820636">"Egyes fotók nem tölthetők be"</string>
<string name="dialog_button_text" msgid="351366485240852280">"Értem"</string>
+ <!-- no translation found for permlab_accessMediaOwnerPackageName (3849443148060165651) -->
+ <skip />
+ <!-- no translation found for permdesc_accessMediaOwnerPackageName (7381563109363105371) -->
+ <skip />
</resources>
diff --git a/res/values-hy/strings.xml b/res/values-hy/strings.xml
index 6fee32e..fe984d8 100644
--- a/res/values-hy/strings.xml
+++ b/res/values-hy/strings.xml
@@ -66,11 +66,20 @@
<string name="picker_preview" msgid="6257414886055861039">"Նախադիտում"</string>
<string name="picker_work_profile" msgid="2083221066869141576">"Բացել աշխատանքային պրոֆիլը"</string>
<string name="picker_personal_profile" msgid="639484258397758406">"Բացել անձնական պրոֆիլը"</string>
+ <string name="picker_personal_profile_label" msgid="6189198163209597344">"Անձնական"</string>
<string name="picker_profile_admin_title" msgid="4172022376418293777">"Արգելափակվել է ձեր ադմինիստրատորի կողմից"</string>
<string name="picker_profile_admin_msg_from_personal" msgid="1941639895084555723">"Աշխատանքային տվյալները հասանելի չեն անձնական հավելվածում"</string>
<string name="picker_profile_admin_msg_from_work" msgid="8048524337462790110">"Անձնական տվյալները հասանելի չեն աշխատանքային հավելվածում"</string>
+ <!-- no translation found for picker_profile_admin_msg (4060112887923255178) -->
+ <skip />
+ <!-- no translation found for picker_profile_switch_message (1133817927412489487) -->
+ <skip />
<string name="picker_profile_work_paused_title" msgid="382212880704235925">"Աշխատանքային հավելվածները դադարեցված են"</string>
+ <!-- no translation found for picker_profile_paused_title (2079739512895529028) -->
+ <skip />
<string name="picker_profile_work_paused_msg" msgid="6321552322125246726">"Աշխատանքային լուսանկարները բացելու համար միացրեք աշխատանքային հավելվածները և նորից փորձեք։"</string>
+ <!-- no translation found for picker_profile_paused_msg (1215076898583993782) -->
+ <skip />
<string name="picker_privacy_message" msgid="9132700451027116817">"Այս հավելվածին հասանելի են միայն ձեր ընտրած լուսանկարները"</string>
<string name="picker_header_permissions" msgid="675872774407768495">"Ընտրեք լուսանկարներ և տեսանյութեր, որոնք ուզում եք հասանելի դարձնել այս հավելվածին"</string>
<string name="picker_album_item_count" msgid="4420723302534177596">"{count,plural, =1{<xliff:g id="COUNT_0">^1</xliff:g> տարր}one{<xliff:g id="COUNT_1">^1</xliff:g> տարր}other{<xliff:g id="COUNT_1">^1</xliff:g> տարր}}"</string>
@@ -160,4 +169,8 @@
<string name="dialog_error_message" msgid="5120432204743681606">"Փորձեք ավելի ուշ։ Ձեր լուսանկարները հասանելի կլինեն, երբ խնդիրը լուծվի։"</string>
<string name="dialog_error_title" msgid="636349284077820636">"Չհաջողվեց բեռնել որոշ լուսանկարներ"</string>
<string name="dialog_button_text" msgid="351366485240852280">"Եղավ"</string>
+ <!-- no translation found for permlab_accessMediaOwnerPackageName (3849443148060165651) -->
+ <skip />
+ <!-- no translation found for permdesc_accessMediaOwnerPackageName (7381563109363105371) -->
+ <skip />
</resources>
diff --git a/res/values-in/strings.xml b/res/values-in/strings.xml
index e655fcb..de74fc7 100644
--- a/res/values-in/strings.xml
+++ b/res/values-in/strings.xml
@@ -66,11 +66,20 @@
<string name="picker_preview" msgid="6257414886055861039">"Pratinjau"</string>
<string name="picker_work_profile" msgid="2083221066869141576">"Beralih ke profil kerja"</string>
<string name="picker_personal_profile" msgid="639484258397758406">"Beralih ke profil pribadi"</string>
+ <string name="picker_personal_profile_label" msgid="6189198163209597344">"Pribadi"</string>
<string name="picker_profile_admin_title" msgid="4172022376418293777">"Diblokir oleh admin Anda"</string>
<string name="picker_profile_admin_msg_from_personal" msgid="1941639895084555723">"Mengakses data kerja dari aplikasi pribadi tidak diizinkan"</string>
<string name="picker_profile_admin_msg_from_work" msgid="8048524337462790110">"Mengakses data pribadi dari aplikasi kerja tidak diizinkan"</string>
+ <!-- no translation found for picker_profile_admin_msg (4060112887923255178) -->
+ <skip />
+ <!-- no translation found for picker_profile_switch_message (1133817927412489487) -->
+ <skip />
<string name="picker_profile_work_paused_title" msgid="382212880704235925">"Aplikasi kerja dijeda"</string>
+ <!-- no translation found for picker_profile_paused_title (2079739512895529028) -->
+ <skip />
<string name="picker_profile_work_paused_msg" msgid="6321552322125246726">"Untuk membuka foto kerja, aktifkan aplikasi kerja lalu coba lagi"</string>
+ <!-- no translation found for picker_profile_paused_msg (1215076898583993782) -->
+ <skip />
<string name="picker_privacy_message" msgid="9132700451027116817">"Aplikasi ini hanya dapat mengakses foto yang Anda pilih"</string>
<string name="picker_header_permissions" msgid="675872774407768495">"Pilih foto dan video yang Anda izinkan untuk diakses oleh aplikasi ini"</string>
<string name="picker_album_item_count" msgid="4420723302534177596">"{count,plural, =1{<xliff:g id="COUNT_0">^1</xliff:g> item}other{<xliff:g id="COUNT_1">^1</xliff:g> item}}"</string>
@@ -160,4 +169,8 @@
<string name="dialog_error_message" msgid="5120432204743681606">"Coba lagi nanti. Foto Anda akan tersedia setelah masalah diselesaikan."</string>
<string name="dialog_error_title" msgid="636349284077820636">"Tidak dapat memuat beberapa Foto"</string>
<string name="dialog_button_text" msgid="351366485240852280">"Oke"</string>
+ <!-- no translation found for permlab_accessMediaOwnerPackageName (3849443148060165651) -->
+ <skip />
+ <!-- no translation found for permdesc_accessMediaOwnerPackageName (7381563109363105371) -->
+ <skip />
</resources>
diff --git a/res/values-is/strings.xml b/res/values-is/strings.xml
index 02ea74b..42b230e 100644
--- a/res/values-is/strings.xml
+++ b/res/values-is/strings.xml
@@ -66,11 +66,20 @@
<string name="picker_preview" msgid="6257414886055861039">"Forskoða"</string>
<string name="picker_work_profile" msgid="2083221066869141576">"Skipta yfir í vinnusnið"</string>
<string name="picker_personal_profile" msgid="639484258397758406">"Skipta yfir í einkasnið"</string>
+ <string name="picker_personal_profile_label" msgid="6189198163209597344">"Einkasnið"</string>
<string name="picker_profile_admin_title" msgid="4172022376418293777">"Útilokað af kerfisstjóra"</string>
<string name="picker_profile_admin_msg_from_personal" msgid="1941639895084555723">"Óheimilt er að opna vinnugögn í forriti til einkanota"</string>
<string name="picker_profile_admin_msg_from_work" msgid="8048524337462790110">"Óheimilt er að opna einkagögn í vinnuforriti"</string>
+ <!-- no translation found for picker_profile_admin_msg (4060112887923255178) -->
+ <skip />
+ <!-- no translation found for picker_profile_switch_message (1133817927412489487) -->
+ <skip />
<string name="picker_profile_work_paused_title" msgid="382212880704235925">"Hlé gert á vinnuforritum"</string>
+ <!-- no translation found for picker_profile_paused_title (2079739512895529028) -->
+ <skip />
<string name="picker_profile_work_paused_msg" msgid="6321552322125246726">"Kveiktu á vinnuforritunum og reyndu síðan að opna vinnumyndirnar aftur"</string>
+ <!-- no translation found for picker_profile_paused_msg (1215076898583993782) -->
+ <skip />
<string name="picker_privacy_message" msgid="9132700451027116817">"Þetta forrit hefur aðeins aðgang að myndunum sem þú velur"</string>
<string name="picker_header_permissions" msgid="675872774407768495">"Veldu myndir og myndskeið sem þetta forrit má fá aðgang að"</string>
<string name="picker_album_item_count" msgid="4420723302534177596">"{count,plural, =1{<xliff:g id="COUNT_0">^1</xliff:g> atriði}one{<xliff:g id="COUNT_1">^1</xliff:g> atriði}other{<xliff:g id="COUNT_1">^1</xliff:g> atriði}}"</string>
@@ -160,4 +169,8 @@
<string name="dialog_error_message" msgid="5120432204743681606">"Reyndu aftur síðar. Myndirnar þínar verða tiltækar um leið og vandamálið er leyst."</string>
<string name="dialog_error_title" msgid="636349284077820636">"Ekki tekst að hlaða sumum myndum"</string>
<string name="dialog_button_text" msgid="351366485240852280">"Ég skil"</string>
+ <!-- no translation found for permlab_accessMediaOwnerPackageName (3849443148060165651) -->
+ <skip />
+ <!-- no translation found for permdesc_accessMediaOwnerPackageName (7381563109363105371) -->
+ <skip />
</resources>
diff --git a/res/values-it/strings.xml b/res/values-it/strings.xml
index e86dc97..10af0cc 100644
--- a/res/values-it/strings.xml
+++ b/res/values-it/strings.xml
@@ -66,11 +66,20 @@
<string name="picker_preview" msgid="6257414886055861039">"Anteprima"</string>
<string name="picker_work_profile" msgid="2083221066869141576">"Passa al profilo di lavoro"</string>
<string name="picker_personal_profile" msgid="639484258397758406">"Passa al profilo personale"</string>
+ <string name="picker_personal_profile_label" msgid="6189198163209597344">"Personale"</string>
<string name="picker_profile_admin_title" msgid="4172022376418293777">"Accesso bloccato dall\'amministratore"</string>
<string name="picker_profile_admin_msg_from_personal" msgid="1941639895084555723">"Non è consentito accedere ai dati di lavoro da un\'app personale"</string>
<string name="picker_profile_admin_msg_from_work" msgid="8048524337462790110">"Non è consentito accedere ai dati personali da un\'app di lavoro"</string>
+ <!-- no translation found for picker_profile_admin_msg (4060112887923255178) -->
+ <skip />
+ <!-- no translation found for picker_profile_switch_message (1133817927412489487) -->
+ <skip />
<string name="picker_profile_work_paused_title" msgid="382212880704235925">"Le app di lavoro sono in pausa"</string>
+ <!-- no translation found for picker_profile_paused_title (2079739512895529028) -->
+ <skip />
<string name="picker_profile_work_paused_msg" msgid="6321552322125246726">"Per aprire le foto relative al lavoro, attiva le app di lavoro e riprova"</string>
+ <!-- no translation found for picker_profile_paused_msg (1215076898583993782) -->
+ <skip />
<string name="picker_privacy_message" msgid="9132700451027116817">"Questa app può accedere soltanto alle foto che selezioni"</string>
<string name="picker_header_permissions" msgid="675872774407768495">"Seleziona le foto e i video a cui può accedere questa app"</string>
<string name="picker_album_item_count" msgid="4420723302534177596">"{count,plural, =1{<xliff:g id="COUNT_0">^1</xliff:g> elemento}many{<xliff:g id="COUNT_1">^1</xliff:g> elementi}other{<xliff:g id="COUNT_1">^1</xliff:g> elementi}}"</string>
@@ -160,4 +169,8 @@
<string name="dialog_error_message" msgid="5120432204743681606">"Riprova più tardi. Le tue foto saranno disponibili dopo aver risolto il problema."</string>
<string name="dialog_error_title" msgid="636349284077820636">"Impossibile caricare alcune foto"</string>
<string name="dialog_button_text" msgid="351366485240852280">"OK"</string>
+ <!-- no translation found for permlab_accessMediaOwnerPackageName (3849443148060165651) -->
+ <skip />
+ <!-- no translation found for permdesc_accessMediaOwnerPackageName (7381563109363105371) -->
+ <skip />
</resources>
diff --git a/res/values-iw/strings.xml b/res/values-iw/strings.xml
index 31c3197..aea872b 100644
--- a/res/values-iw/strings.xml
+++ b/res/values-iw/strings.xml
@@ -66,11 +66,20 @@
<string name="picker_preview" msgid="6257414886055861039">"תצוגה מקדימה"</string>
<string name="picker_work_profile" msgid="2083221066869141576">"לפרופיל העבודה"</string>
<string name="picker_personal_profile" msgid="639484258397758406">"לפרופיל האישי"</string>
+ <string name="picker_personal_profile_label" msgid="6189198163209597344">"אישי"</string>
<string name="picker_profile_admin_title" msgid="4172022376418293777">"הפעולה נחסמה על ידי מנהל המערכת"</string>
<string name="picker_profile_admin_msg_from_personal" msgid="1941639895084555723">"אי אפשר לגשת לנתוני עבודה דרך אפליקציה לשימוש אישי"</string>
<string name="picker_profile_admin_msg_from_work" msgid="8048524337462790110">"אי אפשר לגשת למידע אישי דרך אפליקציה לעבודה"</string>
+ <!-- no translation found for picker_profile_admin_msg (4060112887923255178) -->
+ <skip />
+ <!-- no translation found for picker_profile_switch_message (1133817927412489487) -->
+ <skip />
<string name="picker_profile_work_paused_title" msgid="382212880704235925">"האפליקציות לעבודה מושהות"</string>
+ <!-- no translation found for picker_profile_paused_title (2079739512895529028) -->
+ <skip />
<string name="picker_profile_work_paused_msg" msgid="6321552322125246726">"כדי לפתוח תמונות מפרופיל העבודה, צריך להפעיל את האפליקציות לעבודה ולנסות שוב"</string>
+ <!-- no translation found for picker_profile_paused_msg (1215076898583993782) -->
+ <skip />
<string name="picker_privacy_message" msgid="9132700451027116817">"לאפליקציה הזו יש גישה רק לתמונות שבחרת"</string>
<string name="picker_header_permissions" msgid="675872774407768495">"צריך לבחור תמונות וסרטונים שהאפליקציה הזו תוכל לגשת אליהם"</string>
<string name="picker_album_item_count" msgid="4420723302534177596">"{count,plural, =1{פריט אחד (<xliff:g id="COUNT_0">^1</xliff:g>)}one{<xliff:g id="COUNT_1">^1</xliff:g> פריטים}two{<xliff:g id="COUNT_1">^1</xliff:g> פריטים}other{<xliff:g id="COUNT_1">^1</xliff:g> פריטים}}"</string>
@@ -160,4 +169,8 @@
<string name="dialog_error_message" msgid="5120432204743681606">"כדאי לנסות שוב אחר כך. התמונות שלך יהיו זמינות כשהבעיה תיפתר."</string>
<string name="dialog_error_title" msgid="636349284077820636">"יש תמונות שאי אפשר לטעון כרגע"</string>
<string name="dialog_button_text" msgid="351366485240852280">"הבנתי"</string>
+ <!-- no translation found for permlab_accessMediaOwnerPackageName (3849443148060165651) -->
+ <skip />
+ <!-- no translation found for permdesc_accessMediaOwnerPackageName (7381563109363105371) -->
+ <skip />
</resources>
diff --git a/res/values-ja/strings.xml b/res/values-ja/strings.xml
index 30a9d9f..69aa736 100644
--- a/res/values-ja/strings.xml
+++ b/res/values-ja/strings.xml
@@ -66,11 +66,20 @@
<string name="picker_preview" msgid="6257414886055861039">"プレビュー"</string>
<string name="picker_work_profile" msgid="2083221066869141576">"仕事用に切り替える"</string>
<string name="picker_personal_profile" msgid="639484258397758406">"個人用に切り替える"</string>
+ <string name="picker_personal_profile_label" msgid="6189198163209597344">"個人"</string>
<string name="picker_profile_admin_title" msgid="4172022376418293777">"管理者によりブロックされています"</string>
<string name="picker_profile_admin_msg_from_personal" msgid="1941639895084555723">"個人用アプリから仕事用データにアクセスすることは許可されていません"</string>
<string name="picker_profile_admin_msg_from_work" msgid="8048524337462790110">"仕事用アプリから個人データにアクセスすることは認められていません"</string>
+ <!-- no translation found for picker_profile_admin_msg (4060112887923255178) -->
+ <skip />
+ <!-- no translation found for picker_profile_switch_message (1133817927412489487) -->
+ <skip />
<string name="picker_profile_work_paused_title" msgid="382212880704235925">"仕事用アプリ一時停止中"</string>
+ <!-- no translation found for picker_profile_paused_title (2079739512895529028) -->
+ <skip />
<string name="picker_profile_work_paused_msg" msgid="6321552322125246726">"仕事用の写真を開くには、仕事用アプリを有効にしてからもう一度試してください。"</string>
+ <!-- no translation found for picker_profile_paused_msg (1215076898583993782) -->
+ <skip />
<string name="picker_privacy_message" msgid="9132700451027116817">"このアプリは、選択した写真にのみアクセスできます。"</string>
<string name="picker_header_permissions" msgid="675872774407768495">"このアプリにアクセスを許可する写真と動画を選択してください"</string>
<string name="picker_album_item_count" msgid="4420723302534177596">"{count,plural, =1{<xliff:g id="COUNT_0">^1</xliff:g> 件のアイテム}other{<xliff:g id="COUNT_1">^1</xliff:g> 件のアイテム}}"</string>
@@ -160,4 +169,6 @@
<string name="dialog_error_message" msgid="5120432204743681606">"しばらくしてからもう一度お試しください。問題が解決されると、写真をご利用いただけるようになります。"</string>
<string name="dialog_error_title" msgid="636349284077820636">"読み込めなかった写真があります"</string>
<string name="dialog_button_text" msgid="351366485240852280">"OK"</string>
+ <string name="permlab_accessMediaOwnerPackageName" msgid="3849443148060165651">"メディア所有者のパッケージ名へのアクセス"</string>
+ <string name="permdesc_accessMediaOwnerPackageName" msgid="7381563109363105371">"アクセス可能なすべてのメディア ファイルの所有者パッケージ名へのアクセスをアプリに許可します。"</string>
</resources>
diff --git a/res/values-ka/strings.xml b/res/values-ka/strings.xml
index 27398e9..8ab20ef 100644
--- a/res/values-ka/strings.xml
+++ b/res/values-ka/strings.xml
@@ -66,11 +66,20 @@
<string name="picker_preview" msgid="6257414886055861039">"გადახედვა"</string>
<string name="picker_work_profile" msgid="2083221066869141576">"სამსახურზე გადართვა"</string>
<string name="picker_personal_profile" msgid="639484258397758406">"პირად პროფილზე გადართვა"</string>
+ <string name="picker_personal_profile_label" msgid="6189198163209597344">"პირადი"</string>
<string name="picker_profile_admin_title" msgid="4172022376418293777">"დაბლოკილია თქვენი ადმინისტრატორის მიერ"</string>
<string name="picker_profile_admin_msg_from_personal" msgid="1941639895084555723">"პირადი აპიდან სამუშაო სამსახურებრივ მონაცემებზე წვდომა დაუშვებელია"</string>
<string name="picker_profile_admin_msg_from_work" msgid="8048524337462790110">"სამსახურის აპიდან პერსონალურ მონაცემებზე წვდომა დაუშვებელია"</string>
+ <!-- no translation found for picker_profile_admin_msg (4060112887923255178) -->
+ <skip />
+ <!-- no translation found for picker_profile_switch_message (1133817927412489487) -->
+ <skip />
<string name="picker_profile_work_paused_title" msgid="382212880704235925">"სამსახურის აპები დაპაუზებულია"</string>
+ <!-- no translation found for picker_profile_paused_title (2079739512895529028) -->
+ <skip />
<string name="picker_profile_work_paused_msg" msgid="6321552322125246726">"სამსახურის ფოტოების გასახსნელად ჩართეთ თქვენი სამსახურის აპები და შემდეგ ცადეთ ხელახლა"</string>
+ <!-- no translation found for picker_profile_paused_msg (1215076898583993782) -->
+ <skip />
<string name="picker_privacy_message" msgid="9132700451027116817">"ეს აპი მხოლოდ თქვენ მიერ მონიშნულ ფოტოებზე წვდომას შეძლებს"</string>
<string name="picker_header_permissions" msgid="675872774407768495">"შეარჩიეთ ფოტოები და ვიდეოები, რომლებზეც ამ აპს აქვს წვდომა"</string>
<string name="picker_album_item_count" msgid="4420723302534177596">"{count,plural, =1{<xliff:g id="COUNT_0">^1</xliff:g> ერთეული}other{<xliff:g id="COUNT_1">^1</xliff:g> ერთეული}}"</string>
@@ -160,4 +169,8 @@
<string name="dialog_error_message" msgid="5120432204743681606">"ცადეთ მოგვიანებით. თქვენი ფოტოები ხარვეზის აღმოფხვრის შემდეგ იქნება ხელმისაწვდომი."</string>
<string name="dialog_error_title" msgid="636349284077820636">"ზოგიერთი ფოტოს ჩატვირთვა ვერ ხერხდება"</string>
<string name="dialog_button_text" msgid="351366485240852280">"გასაგებია"</string>
+ <!-- no translation found for permlab_accessMediaOwnerPackageName (3849443148060165651) -->
+ <skip />
+ <!-- no translation found for permdesc_accessMediaOwnerPackageName (7381563109363105371) -->
+ <skip />
</resources>
diff --git a/res/values-kk/strings.xml b/res/values-kk/strings.xml
index 28cd54b..49b3021 100644
--- a/res/values-kk/strings.xml
+++ b/res/values-kk/strings.xml
@@ -66,11 +66,20 @@
<string name="picker_preview" msgid="6257414886055861039">"Алғы көрініс"</string>
<string name="picker_work_profile" msgid="2083221066869141576">"Жұмыс профиліне ауысу"</string>
<string name="picker_personal_profile" msgid="639484258397758406">"Жеке профильге ауысу"</string>
+ <string name="picker_personal_profile_label" msgid="6189198163209597344">"Жеке"</string>
<string name="picker_profile_admin_title" msgid="4172022376418293777">"Әкімшіңіз бөгеген"</string>
<string name="picker_profile_admin_msg_from_personal" msgid="1941639895084555723">"Жұмыс дерегіне жеке қолданбадан кіруге рұқсат жоқ."</string>
<string name="picker_profile_admin_msg_from_work" msgid="8048524337462790110">"Жеке дерекке жұмыс қолданбасынан кіруге рұқсат жоқ."</string>
+ <!-- no translation found for picker_profile_admin_msg (4060112887923255178) -->
+ <skip />
+ <!-- no translation found for picker_profile_switch_message (1133817927412489487) -->
+ <skip />
<string name="picker_profile_work_paused_title" msgid="382212880704235925">"Жұмыс қолданбалары кідіртілді"</string>
+ <!-- no translation found for picker_profile_paused_title (2079739512895529028) -->
+ <skip />
<string name="picker_profile_work_paused_msg" msgid="6321552322125246726">"Жұмыс аккаунтындағы фотосуреттерді ашу үшін жұмыс қолданбаларын қосып, содан кейін қайталап көріңіз."</string>
+ <!-- no translation found for picker_profile_paused_msg (1215076898583993782) -->
+ <skip />
<string name="picker_privacy_message" msgid="9132700451027116817">"Бұл қолданба өзіңіз таңдаған фотосуреттерді ғана пайдалана алады."</string>
<string name="picker_header_permissions" msgid="675872774407768495">"Осы қолданба пайдалана алатын фотосуреттер мен бейнелерді таңдаңыз"</string>
<string name="picker_album_item_count" msgid="4420723302534177596">"{count,plural, =1{<xliff:g id="COUNT_0">^1</xliff:g> элемент}other{<xliff:g id="COUNT_1">^1</xliff:g> элемент}}"</string>
@@ -160,4 +169,8 @@
<string name="dialog_error_message" msgid="5120432204743681606">"Кейінірек қайталап көріңіз. Мәселе шешілген соң, фотосуреттеріңіз қолжетімді болады."</string>
<string name="dialog_error_title" msgid="636349284077820636">"Кейбір фотосуреттерді жүктеу мүмкін емес"</string>
<string name="dialog_button_text" msgid="351366485240852280">"Түсінікті"</string>
+ <!-- no translation found for permlab_accessMediaOwnerPackageName (3849443148060165651) -->
+ <skip />
+ <!-- no translation found for permdesc_accessMediaOwnerPackageName (7381563109363105371) -->
+ <skip />
</resources>
diff --git a/res/values-km/strings.xml b/res/values-km/strings.xml
index 6b10fc5..120e630 100644
--- a/res/values-km/strings.xml
+++ b/res/values-km/strings.xml
@@ -66,11 +66,20 @@
<string name="picker_preview" msgid="6257414886055861039">"មើលសាកល្បង"</string>
<string name="picker_work_profile" msgid="2083221066869141576">"ប្ដូរទៅកម្រងព័ត៌មានការងារ"</string>
<string name="picker_personal_profile" msgid="639484258397758406">"ប្ដូរទៅកម្រងព័ត៌មានផ្ទាល់ខ្លួន"</string>
+ <string name="picker_personal_profile_label" msgid="6189198163209597344">"ផ្ទាល់ខ្លួន"</string>
<string name="picker_profile_admin_title" msgid="4172022376418293777">"បានទប់ស្កាត់ដោយអ្នកគ្រប់គ្រងរបស់អ្នក"</string>
<string name="picker_profile_admin_msg_from_personal" msgid="1941639895084555723">"ការចូលប្រើទិន្នន័យការងារពីកម្មវិធីផ្ទាល់ខ្លួនមិនត្រូវបានអនុញ្ញាតទេ"</string>
<string name="picker_profile_admin_msg_from_work" msgid="8048524337462790110">"ការចូលប្រើទិន្នន័យផ្ទាល់ខ្លួនពីកម្មវិធីការងារមិនត្រូវបានអនុញ្ញាតទេ"</string>
+ <!-- no translation found for picker_profile_admin_msg (4060112887923255178) -->
+ <skip />
+ <!-- no translation found for picker_profile_switch_message (1133817927412489487) -->
+ <skip />
<string name="picker_profile_work_paused_title" msgid="382212880704235925">"កម្មវិធីការងារត្រូវបានផ្អាក"</string>
+ <!-- no translation found for picker_profile_paused_title (2079739512895529028) -->
+ <skip />
<string name="picker_profile_work_paused_msg" msgid="6321552322125246726">"ដើម្បីបើករូបថតការងារ សូមបើកកម្មវិធីការងាររបស់អ្នក រួចព្យាយាមម្ដងទៀត"</string>
+ <!-- no translation found for picker_profile_paused_msg (1215076898583993782) -->
+ <skip />
<string name="picker_privacy_message" msgid="9132700451027116817">"កម្មវិធីនេះអាចចូលប្រើបានតែរូបថតដែលអ្នកជ្រើសរើសប៉ុណ្ណោះ"</string>
<string name="picker_header_permissions" msgid="675872774407768495">"ជ្រើសរើសរូបថត និងវីដេអូដែលអ្នកអនុញ្ញាតឱ្យកម្មវិធីនេះចូលប្រើ"</string>
<string name="picker_album_item_count" msgid="4420723302534177596">"{count,plural, =1{ធាតុ <xliff:g id="COUNT_0">^1</xliff:g>}other{ធាតុ <xliff:g id="COUNT_1">^1</xliff:g>}}"</string>
@@ -160,4 +169,8 @@
<string name="dialog_error_message" msgid="5120432204743681606">"សូមព្យាយាមម្តងទៀតនៅពេលក្រោយ។ រូបថតរបស់អ្នកនឹងអាចប្រើបាន បន្ទាប់ពីដោះស្រាយបញ្ហា។"</string>
<string name="dialog_error_title" msgid="636349284077820636">"មិនអាចផ្ទុករូបថតមួយចំនួនបានទេ"</string>
<string name="dialog_button_text" msgid="351366485240852280">"យល់ហើយ"</string>
+ <!-- no translation found for permlab_accessMediaOwnerPackageName (3849443148060165651) -->
+ <skip />
+ <!-- no translation found for permdesc_accessMediaOwnerPackageName (7381563109363105371) -->
+ <skip />
</resources>
diff --git a/res/values-kn/strings.xml b/res/values-kn/strings.xml
index 7e3af0b..754584e 100644
--- a/res/values-kn/strings.xml
+++ b/res/values-kn/strings.xml
@@ -66,11 +66,20 @@
<string name="picker_preview" msgid="6257414886055861039">"ಪೂರ್ವವೀಕ್ಷಣೆ"</string>
<string name="picker_work_profile" msgid="2083221066869141576">"ಉದ್ಯೋಗ ಪ್ರೊಫೈಲ್ಗೆ ಬದಲಿಸಿ"</string>
<string name="picker_personal_profile" msgid="639484258397758406">"ವೈಯಕ್ತಿಕ ಪ್ರೊಫೈಲ್ಗೆ ಬದಲಿಸಿ"</string>
+ <string name="picker_personal_profile_label" msgid="6189198163209597344">"ವೈಯಕ್ತಿಕ"</string>
<string name="picker_profile_admin_title" msgid="4172022376418293777">"ನಿಮ್ಮ ನಿರ್ವಾಹಕರು ನಿರ್ಬಂಧಿಸಿದ್ದಾರೆ"</string>
<string name="picker_profile_admin_msg_from_personal" msgid="1941639895084555723">"ವೈಯಕ್ತಿಕ ಆ್ಯಪ್ನಿಂದ ಉದ್ಯೋಗದ ಡೇಟಾವನ್ನು ಆ್ಯಕ್ಸೆಸ್ ಮಾಡಲು ಅನುಮತಿಸಲಾಗುವುದಿಲ್ಲ"</string>
<string name="picker_profile_admin_msg_from_work" msgid="8048524337462790110">"ಕೆಲಸಕ್ಕೆ ಸಂಬಂಧಿಸಿದ ಆ್ಯಪ್ ಮೂಲಕ ಅಧಿಕೃತ ಡೇಟಾವನ್ನು ಪ್ರವೇಶಿಸಲು ಅನುಮತಿಸಲಾಗುವುದಿಲ್ಲ"</string>
+ <!-- no translation found for picker_profile_admin_msg (4060112887923255178) -->
+ <skip />
+ <!-- no translation found for picker_profile_switch_message (1133817927412489487) -->
+ <skip />
<string name="picker_profile_work_paused_title" msgid="382212880704235925">"ಕೆಲಸಕ್ಕೆ ಸಂಬಂಧಿಸಿದ ಆ್ಯಪ್ಗಳನ್ನು ವಿರಾಮಗೊಳಿಸಲಾಗಿದೆ"</string>
+ <!-- no translation found for picker_profile_paused_title (2079739512895529028) -->
+ <skip />
<string name="picker_profile_work_paused_msg" msgid="6321552322125246726">"ಕೆಲಸದ ಫೋಟೋಗಳನ್ನು ತೆರೆಯಲು, ನಿಮ್ಮ ಕೆಲಸಕ್ಕೆ ಸಂಬಂಧಿಸಿದ ಆ್ಯಪ್ಗಳನ್ನು ಆನ್ ಮಾಡಿ ನಂತರ ಪುನಃ ಪ್ರಯತ್ನಿಸಿ"</string>
+ <!-- no translation found for picker_profile_paused_msg (1215076898583993782) -->
+ <skip />
<string name="picker_privacy_message" msgid="9132700451027116817">"ಈ ಆ್ಯಪ್ ನೀವು ಆಯ್ಕೆಮಾಡಿದ ಫೋಟೋಗಳನ್ನು ಮಾತ್ರ ಆ್ಯಕ್ಸೆಸ್ ಮಾಡಬಹುದು"</string>
<string name="picker_header_permissions" msgid="675872774407768495">"ಈ ಆ್ಯಪ್ ಅನ್ನು ಆ್ಯಕ್ಸೆಸ್ ಮಾಡಲು ನೀವು ಅನುಮತಿಸುವ ಫೋಟೋಗಳು ಮತ್ತು ವೀಡಿಯೊಗಳನ್ನು ಆಯ್ಕೆಮಾಡಿ"</string>
<string name="picker_album_item_count" msgid="4420723302534177596">"{count,plural, =1{<xliff:g id="COUNT_0">^1</xliff:g> ಐಟಂ}one{<xliff:g id="COUNT_1">^1</xliff:g> ಐಟಂಗಳು}other{<xliff:g id="COUNT_1">^1</xliff:g> ಐಟಂಗಳು}}"</string>
@@ -160,4 +169,8 @@
<string name="dialog_error_message" msgid="5120432204743681606">"ನಂತರ ಪುನಃ ಪ್ರಯತ್ನಿಸಿ. ಸಮಸ್ಯೆ ಬಗೆಹರಿದ ನಂತರ ನಿಮ್ಮ ಫೋಟೋಗಳು ಲಭ್ಯವಿರುತ್ತವೆ."</string>
<string name="dialog_error_title" msgid="636349284077820636">"ಕೆಲವು ಫೋಟೋಗಳನ್ನು ಲೋಡ್ ಮಾಡಲು ಸಾಧ್ಯವಾಗುತ್ತಿಲ್ಲ"</string>
<string name="dialog_button_text" msgid="351366485240852280">"ಅರ್ಥವಾಯಿತು"</string>
+ <!-- no translation found for permlab_accessMediaOwnerPackageName (3849443148060165651) -->
+ <skip />
+ <!-- no translation found for permdesc_accessMediaOwnerPackageName (7381563109363105371) -->
+ <skip />
</resources>
diff --git a/res/values-ko/strings.xml b/res/values-ko/strings.xml
index 3b7432a..b2c0187 100644
--- a/res/values-ko/strings.xml
+++ b/res/values-ko/strings.xml
@@ -66,11 +66,20 @@
<string name="picker_preview" msgid="6257414886055861039">"미리보기"</string>
<string name="picker_work_profile" msgid="2083221066869141576">"직장 프로필로 전환"</string>
<string name="picker_personal_profile" msgid="639484258397758406">"개인 프로필로 전환"</string>
+ <string name="picker_personal_profile_label" msgid="6189198163209597344">"개인"</string>
<string name="picker_profile_admin_title" msgid="4172022376418293777">"관리자가 차단함"</string>
<string name="picker_profile_admin_msg_from_personal" msgid="1941639895084555723">"개인 앱에서는 업무 데이터에 액세스할 수 없습니다."</string>
<string name="picker_profile_admin_msg_from_work" msgid="8048524337462790110">"직장 앱에서는 개인 데이터에 액세스할 수 없습니다."</string>
+ <!-- no translation found for picker_profile_admin_msg (4060112887923255178) -->
+ <skip />
+ <!-- no translation found for picker_profile_switch_message (1133817927412489487) -->
+ <skip />
<string name="picker_profile_work_paused_title" msgid="382212880704235925">"직장 앱이 일시중지됨"</string>
+ <!-- no translation found for picker_profile_paused_title (2079739512895529028) -->
+ <skip />
<string name="picker_profile_work_paused_msg" msgid="6321552322125246726">"직장 사진을 열려면 직장 앱을 사용 설정한 후 다시 시도하세요."</string>
+ <!-- no translation found for picker_profile_paused_msg (1215076898583993782) -->
+ <skip />
<string name="picker_privacy_message" msgid="9132700451027116817">"앱은 사용자가 선택한 사진에만 액세스할 수 있습니다."</string>
<string name="picker_header_permissions" msgid="675872774407768495">"이 앱에서 액세스할 수 있도록 허용할 사진과 동영상을 선택하세요"</string>
<string name="picker_album_item_count" msgid="4420723302534177596">"{count,plural, =1{항목 <xliff:g id="COUNT_0">^1</xliff:g>개}other{항목 <xliff:g id="COUNT_1">^1</xliff:g>개}}"</string>
@@ -160,4 +169,8 @@
<string name="dialog_error_message" msgid="5120432204743681606">"나중에 다시 시도해 주세요. 문제가 해결된 후 사진을 사용할 수 있습니다."</string>
<string name="dialog_error_title" msgid="636349284077820636">"일부 사진을 로드할 수 없음"</string>
<string name="dialog_button_text" msgid="351366485240852280">"확인"</string>
+ <!-- no translation found for permlab_accessMediaOwnerPackageName (3849443148060165651) -->
+ <skip />
+ <!-- no translation found for permdesc_accessMediaOwnerPackageName (7381563109363105371) -->
+ <skip />
</resources>
diff --git a/res/values-ky/strings.xml b/res/values-ky/strings.xml
index 4757855..26d5539 100644
--- a/res/values-ky/strings.xml
+++ b/res/values-ky/strings.xml
@@ -66,11 +66,20 @@
<string name="picker_preview" msgid="6257414886055861039">"Алдын ала көрүү"</string>
<string name="picker_work_profile" msgid="2083221066869141576">"Жумуш профилине которулуу"</string>
<string name="picker_personal_profile" msgid="639484258397758406">"Жеке профилге которулуу"</string>
+ <string name="picker_personal_profile_label" msgid="6189198163209597344">"Жеке"</string>
<string name="picker_profile_admin_title" msgid="4172022376418293777">"Администраторуңуз бөгөттөп койгон"</string>
<string name="picker_profile_admin_msg_from_personal" msgid="1941639895084555723">"Жумушка байланыштуу маалыматка жеке колдонмодон кирүүгө тыюу салынат"</string>
<string name="picker_profile_admin_msg_from_work" msgid="8048524337462790110">"Жеке маалыматка жумуш колдонмосунан кирүүгө тыюу салынат"</string>
+ <!-- no translation found for picker_profile_admin_msg (4060112887923255178) -->
+ <skip />
+ <!-- no translation found for picker_profile_switch_message (1133817927412489487) -->
+ <skip />
<string name="picker_profile_work_paused_title" msgid="382212880704235925">"Жумуш колдонмолору тындырылды"</string>
+ <!-- no translation found for picker_profile_paused_title (2079739512895529028) -->
+ <skip />
<string name="picker_profile_work_paused_msg" msgid="6321552322125246726">"Жумуш сүрөттөрүн ачуу үчүн жумуш колдонмолорун иштетип, кайра аракет кылыңыз"</string>
+ <!-- no translation found for picker_profile_paused_msg (1215076898583993782) -->
+ <skip />
<string name="picker_privacy_message" msgid="9132700451027116817">"Бул колдонмого сиз тандаган сүрөттөр гана жеткиликтүү"</string>
<string name="picker_header_permissions" msgid="675872774407768495">"Бул колдонмо кире турган сүрөттөрдү жана видеолорду тандаңыз"</string>
<string name="picker_album_item_count" msgid="4420723302534177596">"{count,plural, =1{<xliff:g id="COUNT_0">^1</xliff:g> нерсе}other{<xliff:g id="COUNT_1">^1</xliff:g> нерсе}}"</string>
@@ -160,4 +169,8 @@
<string name="dialog_error_message" msgid="5120432204743681606">"Бир аздан кийин кайталап көрүңүз. Сүрөттөрүңүздү маселе чечилгенден кийин көрө аласыз."</string>
<string name="dialog_error_title" msgid="636349284077820636">"Айрым сүрөттөр жүктөлбөй жатат"</string>
<string name="dialog_button_text" msgid="351366485240852280">"Түшүндүм"</string>
+ <!-- no translation found for permlab_accessMediaOwnerPackageName (3849443148060165651) -->
+ <skip />
+ <!-- no translation found for permdesc_accessMediaOwnerPackageName (7381563109363105371) -->
+ <skip />
</resources>
diff --git a/res/values-lo/strings.xml b/res/values-lo/strings.xml
index 2ba9646..373f4a7 100644
--- a/res/values-lo/strings.xml
+++ b/res/values-lo/strings.xml
@@ -66,11 +66,20 @@
<string name="picker_preview" msgid="6257414886055861039">"ຕົວຢ່າງ"</string>
<string name="picker_work_profile" msgid="2083221066869141576">"ສະຫຼັບໄປໂປຣໄຟລ໌ວຽກ"</string>
<string name="picker_personal_profile" msgid="639484258397758406">"ສະຫຼັບໄປໂປຣໄຟລ໌ສ່ວນຕົວ"</string>
+ <string name="picker_personal_profile_label" msgid="6189198163209597344">"ສ່ວນຕົວ"</string>
<string name="picker_profile_admin_title" msgid="4172022376418293777">"ຖືກບລັອກໄວ້ໂດຍຜູ້ເບິ່ງແຍງຂອງທ່ານ"</string>
<string name="picker_profile_admin_msg_from_personal" msgid="1941639895084555723">"ບໍ່ອະນຸຍາດການເຂົ້າເຖິງຂໍ້ມູນບ່ອນເຮັດວຽກຈາກແອັບສ່ວນຕົວ"</string>
<string name="picker_profile_admin_msg_from_work" msgid="8048524337462790110">"ບໍ່ອະນຸຍາດການເຂົ້າເຖິງຂໍ້ມູນສ່ວນຕົວຈາກແອັບບ່ອນເຮັດວຽກ"</string>
+ <!-- no translation found for picker_profile_admin_msg (4060112887923255178) -->
+ <skip />
+ <!-- no translation found for picker_profile_switch_message (1133817927412489487) -->
+ <skip />
<string name="picker_profile_work_paused_title" msgid="382212880704235925">"ຢຸດແອັບບ່ອນເຮັດວຽກຊົ່ວຄາວແລ້ວ"</string>
+ <!-- no translation found for picker_profile_paused_title (2079739512895529028) -->
+ <skip />
<string name="picker_profile_work_paused_msg" msgid="6321552322125246726">"ເພື່ອເປີດຮູບພາບບ່ອນເຮັດວຽກ, ໃຫ້ເປີດໃຊ້ແອັບບ່ອນເຮັດວຽກຂອງທ່ານແລ້ວລອງໃໝ່"</string>
+ <!-- no translation found for picker_profile_paused_msg (1215076898583993782) -->
+ <skip />
<string name="picker_privacy_message" msgid="9132700451027116817">"ແອັບນີ້ສາມາດເຂົ້າເຖິງຮູບພາບທີ່ທ່ານເລືອກເທົ່ານັ້ນ"</string>
<string name="picker_header_permissions" msgid="675872774407768495">"ເລືອກຮູບພາບ ແລະ ວິດີໂອທີ່ທ່ານອະນຸຍາດໃຫ້ແອັບນີ້ເຂົ້າເຖິງໄດ້"</string>
<string name="picker_album_item_count" msgid="4420723302534177596">"{count,plural, =1{<xliff:g id="COUNT_0">^1</xliff:g> ລາຍການ}other{<xliff:g id="COUNT_1">^1</xliff:g> ລາຍການ}}"</string>
@@ -160,4 +169,6 @@
<string name="dialog_error_message" msgid="5120432204743681606">"ກະລຸນາລອງໃໝ່ໃນພາຍຫຼັງ. ຈະມີການສະແດງຮູບພາບຂອງທ່ານເມື່ອບັນຫາໄດ້ຮັບການແກ້ໄຂແລ້ວ."</string>
<string name="dialog_error_title" msgid="636349284077820636">"ບໍ່ສາມາດໂຫຼດບາງຮູບພາບໄດ້"</string>
<string name="dialog_button_text" msgid="351366485240852280">"ເຂົ້າໃຈແລ້ວ"</string>
+ <string name="permlab_accessMediaOwnerPackageName" msgid="3849443148060165651">"ເຂົ້າເຖິງຊື່ແພັກເກດຂອງເຈົ້າຂອງສື່"</string>
+ <string name="permdesc_accessMediaOwnerPackageName" msgid="7381563109363105371">"ອະນຸຍາດໃຫ້ແອັບເຂົ້າເຖິງຊື່ແພັກເກດຂອງເຈົ້າຂອງໄຟລ໌ສື່ທີ່ສາມາດເຂົ້າເຖິງໄດ້ທັງໝົດ."</string>
</resources>
diff --git a/res/values-lt/strings.xml b/res/values-lt/strings.xml
index 29a093f..3a6b51c 100644
--- a/res/values-lt/strings.xml
+++ b/res/values-lt/strings.xml
@@ -66,11 +66,20 @@
<string name="picker_preview" msgid="6257414886055861039">"Peržiūra"</string>
<string name="picker_work_profile" msgid="2083221066869141576">"Perjungti į darbo profilį"</string>
<string name="picker_personal_profile" msgid="639484258397758406">"Perjungti į asmeninį profilį"</string>
+ <string name="picker_personal_profile_label" msgid="6189198163209597344">"Asmeninis"</string>
<string name="picker_profile_admin_title" msgid="4172022376418293777">"Užblokavo jūsų administratorius"</string>
<string name="picker_profile_admin_msg_from_personal" msgid="1941639895084555723">"Neleidžiama pasiekti darbo duomenų iš asmeninės programos"</string>
<string name="picker_profile_admin_msg_from_work" msgid="8048524337462790110">"Neleidžiama pasiekti asmens duomenų iš darbo programos"</string>
+ <!-- no translation found for picker_profile_admin_msg (4060112887923255178) -->
+ <skip />
+ <!-- no translation found for picker_profile_switch_message (1133817927412489487) -->
+ <skip />
<string name="picker_profile_work_paused_title" msgid="382212880704235925">"Darbo programos pristabdytos"</string>
+ <!-- no translation found for picker_profile_paused_title (2079739512895529028) -->
+ <skip />
<string name="picker_profile_work_paused_msg" msgid="6321552322125246726">"Norėdami atidaryti darbo nuotraukas, įjunkite darbo programas ir bandykite dar kartą"</string>
+ <!-- no translation found for picker_profile_paused_msg (1215076898583993782) -->
+ <skip />
<string name="picker_privacy_message" msgid="9132700451027116817">"Ši programa gali pasiekti tik jūsų pasirinktas nuotraukas"</string>
<string name="picker_header_permissions" msgid="675872774407768495">"Pasirinkite nuotraukas ir vaizdo įrašus, kuriuos leisite pasiekti šiai programai"</string>
<string name="picker_album_item_count" msgid="4420723302534177596">"{count,plural, =1{<xliff:g id="COUNT_0">^1</xliff:g> elementas}one{<xliff:g id="COUNT_1">^1</xliff:g> elementas}few{<xliff:g id="COUNT_1">^1</xliff:g> elementai}many{<xliff:g id="COUNT_1">^1</xliff:g> elemento}other{<xliff:g id="COUNT_1">^1</xliff:g> elementų}}"</string>
@@ -160,4 +169,8 @@
<string name="dialog_error_message" msgid="5120432204743681606">"Vėliau bandykite dar kartą. Nuotraukos bus pasiekiamos išsprendus problemą."</string>
<string name="dialog_error_title" msgid="636349284077820636">"Nepavyko įkelti kai kurių nuotraukų"</string>
<string name="dialog_button_text" msgid="351366485240852280">"Supratau"</string>
+ <!-- no translation found for permlab_accessMediaOwnerPackageName (3849443148060165651) -->
+ <skip />
+ <!-- no translation found for permdesc_accessMediaOwnerPackageName (7381563109363105371) -->
+ <skip />
</resources>
diff --git a/res/values-lv/strings.xml b/res/values-lv/strings.xml
index ad857f8..e3ca2c3 100644
--- a/res/values-lv/strings.xml
+++ b/res/values-lv/strings.xml
@@ -66,11 +66,20 @@
<string name="picker_preview" msgid="6257414886055861039">"Priekšskatījums"</string>
<string name="picker_work_profile" msgid="2083221066869141576">"Pārslēgties uz darba profilu"</string>
<string name="picker_personal_profile" msgid="639484258397758406">"Pārslēgties uz personīgo profilu"</string>
+ <string name="picker_personal_profile_label" msgid="6189198163209597344">"Personīgais"</string>
<string name="picker_profile_admin_title" msgid="4172022376418293777">"Bloķējis jūsu administrators"</string>
<string name="picker_profile_admin_msg_from_personal" msgid="1941639895084555723">"Piekļuve darba datiem no personīgās lietotnes nav atļauta."</string>
<string name="picker_profile_admin_msg_from_work" msgid="8048524337462790110">"Piekļuve personīgajiem datiem no darba lietotnes nav atļauta."</string>
+ <!-- no translation found for picker_profile_admin_msg (4060112887923255178) -->
+ <skip />
+ <!-- no translation found for picker_profile_switch_message (1133817927412489487) -->
+ <skip />
<string name="picker_profile_work_paused_title" msgid="382212880704235925">"Darba lietotnes ir apturētas"</string>
+ <!-- no translation found for picker_profile_paused_title (2079739512895529028) -->
+ <skip />
<string name="picker_profile_work_paused_msg" msgid="6321552322125246726">"Lai atvērtu darba fotoattēlus, ieslēdziet savas darba lietotnes un pēc tam mēģiniet vēlreiz."</string>
+ <!-- no translation found for picker_profile_paused_msg (1215076898583993782) -->
+ <skip />
<string name="picker_privacy_message" msgid="9132700451027116817">"Šī lietotne var piekļūt tikai jūsu atlasītajiem fotoattēliem."</string>
<string name="picker_header_permissions" msgid="675872774407768495">"Atlasiet fotoattēlus un videoklipus, kam var piekļūt šī lietotne"</string>
<string name="picker_album_item_count" msgid="4420723302534177596">"{count,plural, =1{<xliff:g id="COUNT_0">^1</xliff:g> vienums}zero{<xliff:g id="COUNT_1">^1</xliff:g> vienumu}one{<xliff:g id="COUNT_1">^1</xliff:g> vienums}other{<xliff:g id="COUNT_1">^1</xliff:g> vienumi}}"</string>
@@ -160,4 +169,8 @@
<string name="dialog_error_message" msgid="5120432204743681606">"Vēlāk mēģiniet vēlreiz. Fotoattēli būs pieejami, tiklīdz problēma būs novērsta."</string>
<string name="dialog_error_title" msgid="636349284077820636">"Nevar ielādēt dažus fotoattēlus"</string>
<string name="dialog_button_text" msgid="351366485240852280">"Labi"</string>
+ <!-- no translation found for permlab_accessMediaOwnerPackageName (3849443148060165651) -->
+ <skip />
+ <!-- no translation found for permdesc_accessMediaOwnerPackageName (7381563109363105371) -->
+ <skip />
</resources>
diff --git a/res/values-mk/strings.xml b/res/values-mk/strings.xml
index cb95f54..d6279db 100644
--- a/res/values-mk/strings.xml
+++ b/res/values-mk/strings.xml
@@ -66,11 +66,20 @@
<string name="picker_preview" msgid="6257414886055861039">"Преглед"</string>
<string name="picker_work_profile" msgid="2083221066869141576">"Префрлете се на работен профил"</string>
<string name="picker_personal_profile" msgid="639484258397758406">"Префрлете се на личен профил"</string>
+ <string name="picker_personal_profile_label" msgid="6189198163209597344">"Личен"</string>
<string name="picker_profile_admin_title" msgid="4172022376418293777">"Блокирано од администраторот"</string>
<string name="picker_profile_admin_msg_from_personal" msgid="1941639895084555723">"Не е дозволено пристапување до работни податоци од лична апликација"</string>
<string name="picker_profile_admin_msg_from_work" msgid="8048524337462790110">"Не е дозволено пристапување до лични податоци од работна апликација"</string>
+ <!-- no translation found for picker_profile_admin_msg (4060112887923255178) -->
+ <skip />
+ <!-- no translation found for picker_profile_switch_message (1133817927412489487) -->
+ <skip />
<string name="picker_profile_work_paused_title" msgid="382212880704235925">"Работните апликации се паузирани"</string>
+ <!-- no translation found for picker_profile_paused_title (2079739512895529028) -->
+ <skip />
<string name="picker_profile_work_paused_msg" msgid="6321552322125246726">"За да ги отворите работните фотографии, вклучете ги работните апликации, па обидете се повторно"</string>
+ <!-- no translation found for picker_profile_paused_msg (1215076898583993782) -->
+ <skip />
<string name="picker_privacy_message" msgid="9132700451027116817">"Апликацијава може да пристапи само до фотографиите што ќе ги изберете"</string>
<string name="picker_header_permissions" msgid="675872774407768495">"Изберете ги фотографиите и видеата до кои ѝ дозволувате пристап на апликацијава"</string>
<string name="picker_album_item_count" msgid="4420723302534177596">"{count,plural, =1{<xliff:g id="COUNT_0">^1</xliff:g> ставка}one{<xliff:g id="COUNT_1">^1</xliff:g> ставка}other{<xliff:g id="COUNT_1">^1</xliff:g> ставки}}"</string>
@@ -160,4 +169,8 @@
<string name="dialog_error_message" msgid="5120432204743681606">"Обидете се повторно подоцна. Вашите фотографии ќе бидат достапни откако ќе се реши проблемот."</string>
<string name="dialog_error_title" msgid="636349284077820636">"Некои фотографии не може да се вчитаат"</string>
<string name="dialog_button_text" msgid="351366485240852280">"Сфатив"</string>
+ <!-- no translation found for permlab_accessMediaOwnerPackageName (3849443148060165651) -->
+ <skip />
+ <!-- no translation found for permdesc_accessMediaOwnerPackageName (7381563109363105371) -->
+ <skip />
</resources>
diff --git a/res/values-ml/strings.xml b/res/values-ml/strings.xml
index 5def22b..ad68059 100644
--- a/res/values-ml/strings.xml
+++ b/res/values-ml/strings.xml
@@ -66,11 +66,20 @@
<string name="picker_preview" msgid="6257414886055861039">"പ്രിവ്യു"</string>
<string name="picker_work_profile" msgid="2083221066869141576">"ഔദ്യോഗിക പ്രൊഫൈലിലേക്ക് മാറുക"</string>
<string name="picker_personal_profile" msgid="639484258397758406">"വ്യക്തിഗത പ്രൊഫൈലിലേക്ക് മാറുക"</string>
+ <string name="picker_personal_profile_label" msgid="6189198163209597344">"വ്യക്തിപരം"</string>
<string name="picker_profile_admin_title" msgid="4172022376418293777">"നിങ്ങളുടെ അഡ്മിൻ ബ്ലോക്ക് ചെയ്തിരിക്കുന്നു"</string>
<string name="picker_profile_admin_msg_from_personal" msgid="1941639895084555723">"വ്യക്തിപര ആപ്പിലൂടെ ഔദ്യോഗിക ഡാറ്റ ആക്സസ് ചെയ്യുന്നത് അനുവദനീയമല്ല"</string>
<string name="picker_profile_admin_msg_from_work" msgid="8048524337462790110">"ഔദ്യോഗിക ആപ്പിലൂടെ വ്യക്തിപരമായ ഡാറ്റ ആക്സസ് ചെയ്യുന്നത് അനുവദനീയമല്ല"</string>
+ <!-- no translation found for picker_profile_admin_msg (4060112887923255178) -->
+ <skip />
+ <!-- no translation found for picker_profile_switch_message (1133817927412489487) -->
+ <skip />
<string name="picker_profile_work_paused_title" msgid="382212880704235925">"ഔദ്യോഗിക ആപ്പുകൾ തൽക്കാലികമായി നിർത്തിയിരിക്കുന്നു"</string>
+ <!-- no translation found for picker_profile_paused_title (2079739512895529028) -->
+ <skip />
<string name="picker_profile_work_paused_msg" msgid="6321552322125246726">"ഔദ്യോഗിക ഫോട്ടോകൾ തുറക്കാൻ, നിങ്ങളുടെ ഔദ്യോഗിക ആപ്പുകൾ ഓണാക്കി വീണ്ടും ശ്രമിക്കുക"</string>
+ <!-- no translation found for picker_profile_paused_msg (1215076898583993782) -->
+ <skip />
<string name="picker_privacy_message" msgid="9132700451027116817">"നിങ്ങൾ തിരഞ്ഞെടുത്ത ഫോട്ടോകൾ മാത്രമേ ഈ ആപ്പിന് ആക്സസ് ചെയ്യാനാകൂ"</string>
<string name="picker_header_permissions" msgid="675872774407768495">"നിങ്ങൾ ഈ ആപ്പിന് ആക്സസ് ചെയ്യാൻ അനുമതി നൽകുന്ന ഫോട്ടോകളും വീഡിയോകളും തിരഞ്ഞെടുക്കുക"</string>
<string name="picker_album_item_count" msgid="4420723302534177596">"{count,plural, =1{<xliff:g id="COUNT_0">^1</xliff:g> ഇനം}other{<xliff:g id="COUNT_1">^1</xliff:g> ഇനങ്ങൾ}}"</string>
@@ -160,4 +169,8 @@
<string name="dialog_error_message" msgid="5120432204743681606">"പിന്നീട് വീണ്ടും ശ്രമിക്കുക. പ്രശ്നം പരിഹരിച്ച് കഴിഞ്ഞ് നിങ്ങളുടെ ഫോട്ടോകൾ ലഭ്യമാകും."</string>
<string name="dialog_error_title" msgid="636349284077820636">"ചില ഫോട്ടോകൾ ലോഡ് ചെയ്യാനാകുന്നില്ല"</string>
<string name="dialog_button_text" msgid="351366485240852280">"മനസ്സിലായി"</string>
+ <!-- no translation found for permlab_accessMediaOwnerPackageName (3849443148060165651) -->
+ <skip />
+ <!-- no translation found for permdesc_accessMediaOwnerPackageName (7381563109363105371) -->
+ <skip />
</resources>
diff --git a/res/values-mn/strings.xml b/res/values-mn/strings.xml
index e1a5246..485877a 100644
--- a/res/values-mn/strings.xml
+++ b/res/values-mn/strings.xml
@@ -66,11 +66,20 @@
<string name="picker_preview" msgid="6257414886055861039">"Урьдчилан үзэх"</string>
<string name="picker_work_profile" msgid="2083221066869141576">"Ажлын профайл руу сэлгэх"</string>
<string name="picker_personal_profile" msgid="639484258397758406">"Хувийн профайл руу сэлгэх"</string>
+ <string name="picker_personal_profile_label" msgid="6189198163209597344">"Хувийн"</string>
<string name="picker_profile_admin_title" msgid="4172022376418293777">"Танай админ блоклосон"</string>
<string name="picker_profile_admin_msg_from_personal" msgid="1941639895084555723">"Хувийн аппаас ажлын өгөгдөлд хандахыг зөвшөөрдөггүй"</string>
<string name="picker_profile_admin_msg_from_work" msgid="8048524337462790110">"Ажлын аппаас хувийн өгөгдөлд хандахыг зөвшөөрдөггүй"</string>
+ <!-- no translation found for picker_profile_admin_msg (4060112887923255178) -->
+ <skip />
+ <!-- no translation found for picker_profile_switch_message (1133817927412489487) -->
+ <skip />
<string name="picker_profile_work_paused_title" msgid="382212880704235925">"Ажлын аппуудыг түр зогсоосон"</string>
+ <!-- no translation found for picker_profile_paused_title (2079739512895529028) -->
+ <skip />
<string name="picker_profile_work_paused_msg" msgid="6321552322125246726">"Ажлын зургуудаа нээхийн тулд ажлын аппуудaa асааж, дараа нь дахин оролдоно уу"</string>
+ <!-- no translation found for picker_profile_paused_msg (1215076898583993782) -->
+ <skip />
<string name="picker_privacy_message" msgid="9132700451027116817">"Энэ апп зөвхөн таны сонгосон зургуудад хандах боломжтой"</string>
<string name="picker_header_permissions" msgid="675872774407768495">"Та энэ аппад хандахыг зөвшөөрсөн зураг болон видеог сонгоно уу"</string>
<string name="picker_album_item_count" msgid="4420723302534177596">"{count,plural, =1{<xliff:g id="COUNT_0">^1</xliff:g> зүйл}other{<xliff:g id="COUNT_1">^1</xliff:g> зүйл}}"</string>
@@ -160,4 +169,8 @@
<string name="dialog_error_message" msgid="5120432204743681606">"Дараа дахин оролдоно уу. Асуудлыг шийдвэрлэсний дараа таны зургууд боломжтой болно."</string>
<string name="dialog_error_title" msgid="636349284077820636">"Зарим зургийг ачаалах боломжгүй"</string>
<string name="dialog_button_text" msgid="351366485240852280">"Ойлголоо"</string>
+ <!-- no translation found for permlab_accessMediaOwnerPackageName (3849443148060165651) -->
+ <skip />
+ <!-- no translation found for permdesc_accessMediaOwnerPackageName (7381563109363105371) -->
+ <skip />
</resources>
diff --git a/res/values-mr/strings.xml b/res/values-mr/strings.xml
index 184f72b..4e9c9d7 100644
--- a/res/values-mr/strings.xml
+++ b/res/values-mr/strings.xml
@@ -66,11 +66,20 @@
<string name="picker_preview" msgid="6257414886055861039">"पूर्वावलोकन"</string>
<string name="picker_work_profile" msgid="2083221066869141576">"ऑफिसवर स्विच करा"</string>
<string name="picker_personal_profile" msgid="639484258397758406">"वैयक्तिकवर स्विच करा"</string>
+ <string name="picker_personal_profile_label" msgid="6189198163209597344">"वैयक्तिक"</string>
<string name="picker_profile_admin_title" msgid="4172022376418293777">"तुमच्या ॲडमिनने ब्लॉक केले"</string>
<string name="picker_profile_admin_msg_from_personal" msgid="1941639895084555723">"वैयक्तिक ॲपवरून कामाशी संबंधित डेटा अॅक्सेस करण्याची परवानगी नाही"</string>
<string name="picker_profile_admin_msg_from_work" msgid="8048524337462790110">"कामाशी संबंधित ॲपवरून वैयक्तिक डेटा अॅक्सेस करण्याची परवानगी नाही"</string>
+ <!-- no translation found for picker_profile_admin_msg (4060112887923255178) -->
+ <skip />
+ <!-- no translation found for picker_profile_switch_message (1133817927412489487) -->
+ <skip />
<string name="picker_profile_work_paused_title" msgid="382212880704235925">"कामाशी संबंधित अॅप्स थांबवली आहेत"</string>
+ <!-- no translation found for picker_profile_paused_title (2079739512895529028) -->
+ <skip />
<string name="picker_profile_work_paused_msg" msgid="6321552322125246726">"कामाशी संबंधित फोटो उघडण्यासाठी, कामाशी संबंधित अॅप्स सुरू करा त्यानंतर पुन्हा प्रयत्न करा"</string>
+ <!-- no translation found for picker_profile_paused_msg (1215076898583993782) -->
+ <skip />
<string name="picker_privacy_message" msgid="9132700451027116817">"हे ॲप फक्त तुम्ही निवडलेले फोटो अॅक्सेस करू शकते"</string>
<string name="picker_header_permissions" msgid="675872774407768495">"तुम्ही या ॲपला अॅक्सेस करण्याची अनुमती दिलेले फोटो आणि व्हिडिओ निवडा"</string>
<string name="picker_album_item_count" msgid="4420723302534177596">"{count,plural, =1{<xliff:g id="COUNT_0">^1</xliff:g> आयटम}other{<xliff:g id="COUNT_1">^1</xliff:g> आयटम}}"</string>
@@ -160,4 +169,8 @@
<string name="dialog_error_message" msgid="5120432204743681606">"नंतर पुन्हा प्रयत्न करा. समस्येचे निराकरण झाल्यावर तुमचे फोटो उपलब्ध होतील."</string>
<string name="dialog_error_title" msgid="636349284077820636">"काही फोटो लोड करू शकत नाही"</string>
<string name="dialog_button_text" msgid="351366485240852280">"समजले"</string>
+ <!-- no translation found for permlab_accessMediaOwnerPackageName (3849443148060165651) -->
+ <skip />
+ <!-- no translation found for permdesc_accessMediaOwnerPackageName (7381563109363105371) -->
+ <skip />
</resources>
diff --git a/res/values-ms/strings.xml b/res/values-ms/strings.xml
index 9f48850..c5fbe3c 100644
--- a/res/values-ms/strings.xml
+++ b/res/values-ms/strings.xml
@@ -66,11 +66,20 @@
<string name="picker_preview" msgid="6257414886055861039">"Pratonton"</string>
<string name="picker_work_profile" msgid="2083221066869141576">"Beralih kepada kerja"</string>
<string name="picker_personal_profile" msgid="639484258397758406">"Beralih kepada peribadi"</string>
+ <string name="picker_personal_profile_label" msgid="6189198163209597344">"Peribadi"</string>
<string name="picker_profile_admin_title" msgid="4172022376418293777">"Disekat oleh pentadbir anda"</string>
<string name="picker_profile_admin_msg_from_personal" msgid="1941639895084555723">"Pengaksesan data kerja daripada apl peribadi tidak dibenarkan"</string>
<string name="picker_profile_admin_msg_from_work" msgid="8048524337462790110">"Pengaksesan data peribadi daripada apl kerja tidak dibenarkan"</string>
+ <!-- no translation found for picker_profile_admin_msg (4060112887923255178) -->
+ <skip />
+ <!-- no translation found for picker_profile_switch_message (1133817927412489487) -->
+ <skip />
<string name="picker_profile_work_paused_title" msgid="382212880704235925">"Apl kerja dijeda"</string>
+ <!-- no translation found for picker_profile_paused_title (2079739512895529028) -->
+ <skip />
<string name="picker_profile_work_paused_msg" msgid="6321552322125246726">"Untuk membuka foto kerja, hidupkan apl kerja anda, kemudian cuba lagi"</string>
+ <!-- no translation found for picker_profile_paused_msg (1215076898583993782) -->
+ <skip />
<string name="picker_privacy_message" msgid="9132700451027116817">"Apl ini hanya dapat mengakses foto yang anda pilih"</string>
<string name="picker_header_permissions" msgid="675872774407768495">"Pilih foto dan video yang anda benarkan untuk diakses oleh apl ini"</string>
<string name="picker_album_item_count" msgid="4420723302534177596">"{count,plural, =1{<xliff:g id="COUNT_0">^1</xliff:g> item}other{<xliff:g id="COUNT_1">^1</xliff:g> item}}"</string>
@@ -160,4 +169,6 @@
<string name="dialog_error_message" msgid="5120432204743681606">"Cuba sebentar lagi. Foto anda akan tersedia selepas masalah ini diselesaikan."</string>
<string name="dialog_error_title" msgid="636349284077820636">"Tidak dapat memuatkan beberapa foto"</string>
<string name="dialog_button_text" msgid="351366485240852280">"OK"</string>
+ <string name="permlab_accessMediaOwnerPackageName" msgid="3849443148060165651">"Akses nama pakej pemilik media"</string>
+ <string name="permdesc_accessMediaOwnerPackageName" msgid="7381563109363105371">"Membenarkan apl mengakses nama pakej pemilik bagi semua fail media yang boleh diakses."</string>
</resources>
diff --git a/res/values-my/strings.xml b/res/values-my/strings.xml
index 322eb0a..53add76 100644
--- a/res/values-my/strings.xml
+++ b/res/values-my/strings.xml
@@ -66,11 +66,20 @@
<string name="picker_preview" msgid="6257414886055861039">"အစမ်းကြည့်ရှုခြင်း"</string>
<string name="picker_work_profile" msgid="2083221066869141576">"အလုပ်သို့ ပြောင်းပါ"</string>
<string name="picker_personal_profile" msgid="639484258397758406">"ပုဂ္ဂိုလ်ရေးသီးသန့်အဖြစ် ပြောင်းပါ"</string>
+ <string name="picker_personal_profile_label" msgid="6189198163209597344">"ကိုယ်ပိုင်"</string>
<string name="picker_profile_admin_title" msgid="4172022376418293777">"သင့်စီမံခန့်ခွဲသူက ပိတ်ထားသည်"</string>
<string name="picker_profile_admin_msg_from_personal" msgid="1941639895084555723">"ကိုယ်ရေးသုံးအက်ပ်ဖြင့် အလုပ်ဒေတာများ သုံးခွင့်မရှိပါ"</string>
<string name="picker_profile_admin_msg_from_work" msgid="8048524337462790110">"အလုပ်သုံးအက်ပ်ဖြင့် ကိုယ်ပိုင်ဒေတာများ သုံးခွင့်မရှိပါ"</string>
+ <!-- no translation found for picker_profile_admin_msg (4060112887923255178) -->
+ <skip />
+ <!-- no translation found for picker_profile_switch_message (1133817927412489487) -->
+ <skip />
<string name="picker_profile_work_paused_title" msgid="382212880704235925">"အလုပ်သုံးအက်ပ်များကို ခေတ္တရပ်ထားသည်"</string>
+ <!-- no translation found for picker_profile_paused_title (2079739512895529028) -->
+ <skip />
<string name="picker_profile_work_paused_msg" msgid="6321552322125246726">"အလုပ်ဓာတ်ပုံများဖွင့်ရန် သင့်အလုပ်သုံးအက်ပ်များကိုဖွင့်ပြီး ထပ်စမ်းကြည့်ပါ"</string>
+ <!-- no translation found for picker_profile_paused_msg (1215076898583993782) -->
+ <skip />
<string name="picker_privacy_message" msgid="9132700451027116817">"ဤအက်ပ်သည် သင်ရွေးချယ်သောဓာတ်ပုံများကိုသာ ကြည့်နိုင်သည်"</string>
<string name="picker_header_permissions" msgid="675872774407768495">"ဤအက်ပ်အတွက် သုံးခွင့်ပြုသော ဓာတ်ပုံနှင့် ဗီဒီယိုများ ရွေးရန်"</string>
<string name="picker_album_item_count" msgid="4420723302534177596">"{count,plural, =1{ဖိုင် <xliff:g id="COUNT_0">^1</xliff:g> ခု}other{ဖိုင် <xliff:g id="COUNT_1">^1</xliff:g> ခု}}"</string>
@@ -160,4 +169,8 @@
<string name="dialog_error_message" msgid="5120432204743681606">"နောက်မှထပ်စမ်းပါ။ ပြဿနာကို ဖြေရှင်းပြီးသည့်အခါ သင့်ဓာတ်ပုံများကို ရနိုင်မည်။"</string>
<string name="dialog_error_title" msgid="636349284077820636">"ဓာတ်ပုံအချို့ကို ဖွင့်၍ မရပါ"</string>
<string name="dialog_button_text" msgid="351366485240852280">"နားလည်ပြီ"</string>
+ <!-- no translation found for permlab_accessMediaOwnerPackageName (3849443148060165651) -->
+ <skip />
+ <!-- no translation found for permdesc_accessMediaOwnerPackageName (7381563109363105371) -->
+ <skip />
</resources>
diff --git a/res/values-nb/strings.xml b/res/values-nb/strings.xml
index 060945b..543d6d4 100644
--- a/res/values-nb/strings.xml
+++ b/res/values-nb/strings.xml
@@ -66,11 +66,20 @@
<string name="picker_preview" msgid="6257414886055861039">"Forhåndsvisning"</string>
<string name="picker_work_profile" msgid="2083221066869141576">"Bytt til jobbprofilen"</string>
<string name="picker_personal_profile" msgid="639484258397758406">"Bytt til den personlige profilen"</string>
+ <string name="picker_personal_profile_label" msgid="6189198163209597344">"Personlig"</string>
<string name="picker_profile_admin_title" msgid="4172022376418293777">"Blokkert av administratoren din"</string>
<string name="picker_profile_admin_msg_from_personal" msgid="1941639895084555723">"Du får ikke tilgang til jobbdata fra personlige apper"</string>
<string name="picker_profile_admin_msg_from_work" msgid="8048524337462790110">"Du får ikke tilgang til personlige data fra jobbapper"</string>
+ <!-- no translation found for picker_profile_admin_msg (4060112887923255178) -->
+ <skip />
+ <!-- no translation found for picker_profile_switch_message (1133817927412489487) -->
+ <skip />
<string name="picker_profile_work_paused_title" msgid="382212880704235925">"Jobbapper er satt på pause"</string>
+ <!-- no translation found for picker_profile_paused_title (2079739512895529028) -->
+ <skip />
<string name="picker_profile_work_paused_msg" msgid="6321552322125246726">"For å åpne jobbilder, slå på jobbapper og prøv på nytt"</string>
+ <!-- no translation found for picker_profile_paused_msg (1215076898583993782) -->
+ <skip />
<string name="picker_privacy_message" msgid="9132700451027116817">"Denne appen har bare tilgang til bildene du velger"</string>
<string name="picker_header_permissions" msgid="675872774407768495">"Velg bildene og videoene du lar denne appen bruke"</string>
<string name="picker_album_item_count" msgid="4420723302534177596">"{count,plural, =1{<xliff:g id="COUNT_0">^1</xliff:g> element}other{<xliff:g id="COUNT_1">^1</xliff:g> elementer}}"</string>
@@ -160,4 +169,8 @@
<string name="dialog_error_message" msgid="5120432204743681606">"Prøv på nytt senere. Bildene dine blir tilgjengelige når problemet er løst."</string>
<string name="dialog_error_title" msgid="636349284077820636">"Noen bilder kan ikke lastes inn"</string>
<string name="dialog_button_text" msgid="351366485240852280">"Greit"</string>
+ <!-- no translation found for permlab_accessMediaOwnerPackageName (3849443148060165651) -->
+ <skip />
+ <!-- no translation found for permdesc_accessMediaOwnerPackageName (7381563109363105371) -->
+ <skip />
</resources>
diff --git a/res/values-ne/strings.xml b/res/values-ne/strings.xml
index 5aab7f0..bc5a1af 100644
--- a/res/values-ne/strings.xml
+++ b/res/values-ne/strings.xml
@@ -66,11 +66,20 @@
<string name="picker_preview" msgid="6257414886055861039">"प्रिभ्यू"</string>
<string name="picker_work_profile" msgid="2083221066869141576">"कार्य प्रोफाइल प्रयोग गर्नुहोस्"</string>
<string name="picker_personal_profile" msgid="639484258397758406">"व्यक्तिगत प्रोफाइल प्रयोग गर्नुहोस्"</string>
+ <string name="picker_personal_profile_label" msgid="6189198163209597344">"व्यक्तिगत"</string>
<string name="picker_profile_admin_title" msgid="4172022376418293777">"तपाईंका एड्मिनले ब्लक गरेका छन्"</string>
<string name="picker_profile_admin_msg_from_personal" msgid="1941639895084555723">"व्यक्तिगत एपमार्फत कामसम्बन्धी डेटा हेर्ने वा प्रयोग गर्ने अनुमति दिइएको छैन"</string>
<string name="picker_profile_admin_msg_from_work" msgid="8048524337462790110">"कामसम्बन्धी एपमार्फत व्यक्तिगत जानकारी हेर्ने वा प्रयोग गर्ने अनुमति दिइएको छैन।"</string>
+ <!-- no translation found for picker_profile_admin_msg (4060112887923255178) -->
+ <skip />
+ <!-- no translation found for picker_profile_switch_message (1133817927412489487) -->
+ <skip />
<string name="picker_profile_work_paused_title" msgid="382212880704235925">"कामसम्बन्धी एपहरू पज गरिएका छन्"</string>
+ <!-- no translation found for picker_profile_paused_title (2079739512895529028) -->
+ <skip />
<string name="picker_profile_work_paused_msg" msgid="6321552322125246726">"कामसम्बन्धी फोटोहरू खोल्न आफ्ना कामसम्बन्धी एपहरू अन गर्नुहोस् अनि फेरि प्रयास गर्नुहोस्"</string>
+ <!-- no translation found for picker_profile_paused_msg (1215076898583993782) -->
+ <skip />
<string name="picker_privacy_message" msgid="9132700451027116817">"यो एपले तपाईंले चयन गरेका फोटो मात्र प्रयोग गर्न सक्छ"</string>
<string name="picker_header_permissions" msgid="675872774407768495">"तपाईंले यो एपलाई एक्सेस दिन चाहनुभएका फोटो तथा भिडियोहरू चयन गर्नुहोस्"</string>
<string name="picker_album_item_count" msgid="4420723302534177596">"{count,plural, =1{<xliff:g id="COUNT_0">^1</xliff:g> वटा वस्तु}other{<xliff:g id="COUNT_1">^1</xliff:g> वटा वस्तु}}"</string>
@@ -160,4 +169,8 @@
<string name="dialog_error_message" msgid="5120432204743681606">"पछि फेरि प्रयास गर्नुहोस्। समस्या समाधान हुनेबित्तिकै तपाईंका फोटो उपलब्ध हुने छन्।"</string>
<string name="dialog_error_title" msgid="636349284077820636">"केही फोटोहरू लोड गर्न सकिँदैन"</string>
<string name="dialog_button_text" msgid="351366485240852280">"बुझेँ"</string>
+ <!-- no translation found for permlab_accessMediaOwnerPackageName (3849443148060165651) -->
+ <skip />
+ <!-- no translation found for permdesc_accessMediaOwnerPackageName (7381563109363105371) -->
+ <skip />
</resources>
diff --git a/res/values-nl/strings.xml b/res/values-nl/strings.xml
index df69610..ec88305 100644
--- a/res/values-nl/strings.xml
+++ b/res/values-nl/strings.xml
@@ -66,11 +66,20 @@
<string name="picker_preview" msgid="6257414886055861039">"Voorbeeld"</string>
<string name="picker_work_profile" msgid="2083221066869141576">"Overschakelen naar werkprofiel"</string>
<string name="picker_personal_profile" msgid="639484258397758406">"Overschakelen naar persoonlijk profiel"</string>
+ <string name="picker_personal_profile_label" msgid="6189198163209597344">"Persoonlijk"</string>
<string name="picker_profile_admin_title" msgid="4172022376418293777">"Geblokkeerd door je beheerder"</string>
<string name="picker_profile_admin_msg_from_personal" msgid="1941639895084555723">"Het is niet toegestaan om werkgegevens te openen via een persoonlijke app"</string>
<string name="picker_profile_admin_msg_from_work" msgid="8048524337462790110">"Het is niet toegestaan om persoonsgegevens te openen via een werk-app"</string>
+ <!-- no translation found for picker_profile_admin_msg (4060112887923255178) -->
+ <skip />
+ <!-- no translation found for picker_profile_switch_message (1133817927412489487) -->
+ <skip />
<string name="picker_profile_work_paused_title" msgid="382212880704235925">"Werk-apps zijn onderbroken"</string>
+ <!-- no translation found for picker_profile_paused_title (2079739512895529028) -->
+ <skip />
<string name="picker_profile_work_paused_msg" msgid="6321552322125246726">"Als je werkfoto\'s wilt openen, zet je je werk-apps aan en probeer je het opnieuw"</string>
+ <!-- no translation found for picker_profile_paused_msg (1215076898583993782) -->
+ <skip />
<string name="picker_privacy_message" msgid="9132700451027116817">"Deze app heeft alleen toegang tot de foto\'s die je selecteert"</string>
<string name="picker_header_permissions" msgid="675872774407768495">"Selecteer foto\'s en video\'s waartoe je deze app toegang geeft"</string>
<string name="picker_album_item_count" msgid="4420723302534177596">"{count,plural, =1{<xliff:g id="COUNT_0">^1</xliff:g> item}other{<xliff:g id="COUNT_1">^1</xliff:g> items}}"</string>
@@ -160,4 +169,8 @@
<string name="dialog_error_message" msgid="5120432204743681606">"Probeer het later opnieuw. Je foto\'s komen beschikbaar nadat het probleem is opgelost."</string>
<string name="dialog_error_title" msgid="636349284077820636">"Kan bepaalde foto\'s niet laden"</string>
<string name="dialog_button_text" msgid="351366485240852280">"OK"</string>
+ <!-- no translation found for permlab_accessMediaOwnerPackageName (3849443148060165651) -->
+ <skip />
+ <!-- no translation found for permdesc_accessMediaOwnerPackageName (7381563109363105371) -->
+ <skip />
</resources>
diff --git a/res/values-or/strings.xml b/res/values-or/strings.xml
index 8533864..d875cf2 100644
--- a/res/values-or/strings.xml
+++ b/res/values-or/strings.xml
@@ -66,11 +66,20 @@
<string name="picker_preview" msgid="6257414886055861039">"ପ୍ରିଭ୍ୟୁ"</string>
<string name="picker_work_profile" msgid="2083221066869141576">"ୱାର୍କକୁ ସୁଇଚ କରନ୍ତୁ"</string>
<string name="picker_personal_profile" msgid="639484258397758406">"ବ୍ୟକ୍ତିଗତକୁ ସୁଇଚ କରନ୍ତୁ"</string>
+ <string name="picker_personal_profile_label" msgid="6189198163209597344">"ବ୍ୟକ୍ତିଗତ"</string>
<string name="picker_profile_admin_title" msgid="4172022376418293777">"ଆପଣଙ୍କ ଆଡମିନଙ୍କ ଦ୍ୱାରା ବ୍ଲକ କରାଯାଇଛି"</string>
<string name="picker_profile_admin_msg_from_personal" msgid="1941639895084555723">"କୌଣସି ବ୍ୟକ୍ତିଗତ ଆପରୁ ୱାର୍କ ଡାଟାକୁ ଆକ୍ସେସ କରିବା ପାଇଁ ଅନୁମତି ଦିଆଯାଇନାହିଁ"</string>
<string name="picker_profile_admin_msg_from_work" msgid="8048524337462790110">"କୌଣସି ୱାର୍କ ଆପରୁ ବ୍ୟକ୍ତିଗତ ଡାଟାକୁ ଆକ୍ସେସ୍ କରିବା ପାଇଁ ଅନୁମତି ଦିଆଯାଇନାହିଁ"</string>
+ <!-- no translation found for picker_profile_admin_msg (4060112887923255178) -->
+ <skip />
+ <!-- no translation found for picker_profile_switch_message (1133817927412489487) -->
+ <skip />
<string name="picker_profile_work_paused_title" msgid="382212880704235925">"ୱାର୍କ ଆପଗୁଡ଼ିକୁ ବିରତ କରାଯାଇଛି"</string>
+ <!-- no translation found for picker_profile_paused_title (2079739512895529028) -->
+ <skip />
<string name="picker_profile_work_paused_msg" msgid="6321552322125246726">"ୱାର୍କ ଫଟୋଗୁଡ଼ିକୁ ଖୋଲିବାକୁ, ଆପଣଙ୍କ ୱାର୍କ ଆପଗୁଡ଼ିକୁ ଚାଲୁ କରି ପୁଣି ଚେଷ୍ଟା କରନ୍ତୁ"</string>
+ <!-- no translation found for picker_profile_paused_msg (1215076898583993782) -->
+ <skip />
<string name="picker_privacy_message" msgid="9132700451027116817">"ଏହି ଆପ କେବଳ ଆପଣ ଚୟନ କରିଥିବା ଫଟୋଗୁଡ଼ିକୁ ଆକ୍ସେସ କରିପାରିବ"</string>
<string name="picker_header_permissions" msgid="675872774407768495">"ଆପଣ ଏହି ଆପକୁ ଆକ୍ସେସ କରିବା ପାଇଁ ଅନୁମତି ଦେଉଥିବା ଫଟୋ ଏବଂ ଭିଡିଓଗୁଡ଼ିକୁ ଚୟନ କରନ୍ତୁ"</string>
<string name="picker_album_item_count" msgid="4420723302534177596">"{count,plural, =1{<xliff:g id="COUNT_0">^1</xliff:g>ଟି ଆଇଟମ}other{<xliff:g id="COUNT_1">^1</xliff:g>ଟି ଆଇଟମ}}"</string>
@@ -160,4 +169,8 @@
<string name="dialog_error_message" msgid="5120432204743681606">"ପରେ ପୁଣି ଚେଷ୍ଟା କରନ୍ତୁ। ସମସ୍ୟାର ସମାଧାନ ହେବା ପରେ ଆପଣଙ୍କ ଫଟୋଗୁଡ଼ିକ ଉପଲବ୍ଧ ହେବ।"</string>
<string name="dialog_error_title" msgid="636349284077820636">"କିଛି ଫଟୋ ଲୋଡ କରାଯାଇପାରିବ ନାହିଁ"</string>
<string name="dialog_button_text" msgid="351366485240852280">"ବୁଝିଗଲି"</string>
+ <!-- no translation found for permlab_accessMediaOwnerPackageName (3849443148060165651) -->
+ <skip />
+ <!-- no translation found for permdesc_accessMediaOwnerPackageName (7381563109363105371) -->
+ <skip />
</resources>
diff --git a/res/values-pa/strings.xml b/res/values-pa/strings.xml
index beb933e..7915ab2 100644
--- a/res/values-pa/strings.xml
+++ b/res/values-pa/strings.xml
@@ -66,11 +66,20 @@
<string name="picker_preview" msgid="6257414886055861039">"ਪੂਰਵ-ਝਲਕ"</string>
<string name="picker_work_profile" msgid="2083221066869141576">"ਕਾਰਜ ਪ੍ਰੋਫਾਈਲ \'ਤੇ ਸਵਿੱਚ ਕਰੋ"</string>
<string name="picker_personal_profile" msgid="639484258397758406">"ਨਿੱਜੀ ਪ੍ਰੋਫਾਈਲ \'ਤੇ ਸਵਿੱਚ ਕਰੋ"</string>
+ <string name="picker_personal_profile_label" msgid="6189198163209597344">"ਨਿੱਜੀ"</string>
<string name="picker_profile_admin_title" msgid="4172022376418293777">"ਤੁਹਾਡੇ ਪ੍ਰਸ਼ਾਸਕ ਵੱਲੋਂ ਬਲਾਕ ਕੀਤਾ ਗਿਆ"</string>
<string name="picker_profile_admin_msg_from_personal" msgid="1941639895084555723">"ਨਿੱਜੀ ਐਪ ਤੋਂ ਕਾਰਜ-ਸਥਾਨ ਦੇ ਡਾਟੇ ਤੱਕ ਪਹੁੰਚ ਕਰਨ ਦੀ ਇਜਾਜ਼ਤ ਨਹੀਂ ਹੈ"</string>
<string name="picker_profile_admin_msg_from_work" msgid="8048524337462790110">"ਕੰਮ ਸੰਬੰਧੀ ਐਪ ਤੋਂ ਨਿੱਜੀ ਡਾਟੇ ਤੱਕ ਪਹੁੰਚ ਕਰਨ ਦੀ ਇਜਾਜ਼ਤ ਨਹੀਂ ਹੈ"</string>
+ <!-- no translation found for picker_profile_admin_msg (4060112887923255178) -->
+ <skip />
+ <!-- no translation found for picker_profile_switch_message (1133817927412489487) -->
+ <skip />
<string name="picker_profile_work_paused_title" msgid="382212880704235925">"ਕੰਮ ਸੰਬੰਧੀ ਐਪਾਂ ਨੂੰ ਰੋਕਿਆ ਗਿਆ ਹੈ"</string>
+ <!-- no translation found for picker_profile_paused_title (2079739512895529028) -->
+ <skip />
<string name="picker_profile_work_paused_msg" msgid="6321552322125246726">"ਕੰਮ ਸੰਬੰਧੀ ਫ਼ੋਟੋਆਂ ਖੋਲ੍ਹਣ ਲਈ, ਆਪਣੀਆਂ ਕੰਮ ਸੰਬੰਧੀ ਐਪਾਂ ਨੂੰ ਚਾਲੂ ਕਰੋ ਅਤੇ ਫਿਰ ਦੁਬਾਰਾ ਕੋਸ਼ਿਸ਼ ਕਰੋ"</string>
+ <!-- no translation found for picker_profile_paused_msg (1215076898583993782) -->
+ <skip />
<string name="picker_privacy_message" msgid="9132700451027116817">"ਇਹ ਐਪ ਸਿਰਫ਼ ਤੁਹਾਡੇ ਵੱਲੋਂ ਚੁਣੀਆਂ ਗਈਆਂ ਫ਼ੋਟੋਆਂ ਤੱਕ ਪਹੁੰਚ ਕਰ ਸਕਦੀ ਹੈ"</string>
<string name="picker_header_permissions" msgid="675872774407768495">"ਉਨ੍ਹਾਂ ਫ਼ੋਟੋਆਂ ਅਤੇ ਵੀਡੀਓ ਨੂੰ ਚੁਣੋ ਜਿਨ੍ਹਾਂ ਤੱਕ ਤੁਸੀਂ ਇਸ ਐਪ ਨੂੰ ਪਹੁੰਚ ਕਰਨ ਦੀ ਆਗਿਆ ਦੇਣੀ ਹੈ"</string>
<string name="picker_album_item_count" msgid="4420723302534177596">"{count,plural, =1{<xliff:g id="COUNT_0">^1</xliff:g> ਆਈਟਮ}one{<xliff:g id="COUNT_1">^1</xliff:g> ਆਈਟਮ}other{<xliff:g id="COUNT_1">^1</xliff:g> ਆਈਟਮਾਂ}}"</string>
@@ -155,9 +164,13 @@
<string name="transcode_cancel" msgid="8555752601907598192">"ਰੱਦ ਕਰੋ"</string>
<string name="transcode_wait" msgid="8909773149560697501">"ਉਡੀਕ ਕਰੋ"</string>
<string name="safety_protection_icon_label" msgid="6714354052747723623">"ਸੁਰੱਖਿਆ ਬਚਾਅ"</string>
- <string name="transcode_alert_channel" msgid="997332371757680478">"ਨੇਟਿਵ ਟ੍ਰਾਂਸਕੋਡ ਸੁਚੇਤਨਾਵਾਂ"</string>
+ <string name="transcode_alert_channel" msgid="997332371757680478">"ਨੇਟਿਵ ਟ੍ਰਾਂਸਕੋਡ ਅਲਰਟ"</string>
<string name="transcode_progress_channel" msgid="6905136787933058387">"ਨੇਟਿਵ ਟ੍ਰਾਂਸਕੋਡ ਪ੍ਰਗਤੀ"</string>
<string name="dialog_error_message" msgid="5120432204743681606">"ਬਾਅਦ ਵਿੱਚ ਦੁਬਾਰਾ ਕੋਸ਼ਿਸ਼ ਕਰੋ। ਸਮੱਸਿਆ ਹੱਲ ਹੋਣ ਤੋਂ ਬਾਅਦ ਤੁਹਾਡੀਆਂ ਫ਼ੋਟੋਆਂ ਉਪਲਬਧ ਹੋ ਜਾਣਗੀਆਂ।"</string>
<string name="dialog_error_title" msgid="636349284077820636">"ਕੁਝ ਫ਼ੋਟੋਆਂ ਨੂੰ ਲੋਡ ਨਹੀਂ ਕੀਤਾ ਜਾ ਸਕਦਾ"</string>
<string name="dialog_button_text" msgid="351366485240852280">"ਸਮਝ ਲਿਆ"</string>
+ <!-- no translation found for permlab_accessMediaOwnerPackageName (3849443148060165651) -->
+ <skip />
+ <!-- no translation found for permdesc_accessMediaOwnerPackageName (7381563109363105371) -->
+ <skip />
</resources>
diff --git a/res/values-pl/strings.xml b/res/values-pl/strings.xml
index 6ac9ce3..cba3348 100644
--- a/res/values-pl/strings.xml
+++ b/res/values-pl/strings.xml
@@ -66,11 +66,20 @@
<string name="picker_preview" msgid="6257414886055861039">"Podgląd"</string>
<string name="picker_work_profile" msgid="2083221066869141576">"Włącz profil służbowy"</string>
<string name="picker_personal_profile" msgid="639484258397758406">"Włącz profil osobisty"</string>
+ <string name="picker_personal_profile_label" msgid="6189198163209597344">"Osobisty charakter"</string>
<string name="picker_profile_admin_title" msgid="4172022376418293777">"Czynność zablokowana przez administratora"</string>
<string name="picker_profile_admin_msg_from_personal" msgid="1941639895084555723">"Nie można korzystać z danych służbowych w aplikacji osobistej"</string>
<string name="picker_profile_admin_msg_from_work" msgid="8048524337462790110">"Nie można korzystać z danych osobistych w aplikacji służbowej"</string>
+ <!-- no translation found for picker_profile_admin_msg (4060112887923255178) -->
+ <skip />
+ <!-- no translation found for picker_profile_switch_message (1133817927412489487) -->
+ <skip />
<string name="picker_profile_work_paused_title" msgid="382212880704235925">"Aplikacje służbowe zostały wstrzymane"</string>
+ <!-- no translation found for picker_profile_paused_title (2079739512895529028) -->
+ <skip />
<string name="picker_profile_work_paused_msg" msgid="6321552322125246726">"Aby otworzyć zdjęcia służbowe, włącz aplikacje służbowe i spróbuj ponownie"</string>
+ <!-- no translation found for picker_profile_paused_msg (1215076898583993782) -->
+ <skip />
<string name="picker_privacy_message" msgid="9132700451027116817">"Ta aplikacja ma dostęp tylko do wybranych przez Ciebie zdjęć"</string>
<string name="picker_header_permissions" msgid="675872774407768495">"Wybierz zdjęcia i filmy, do których ta aplikacja ma mieć dostęp"</string>
<string name="picker_album_item_count" msgid="4420723302534177596">"{count,plural, =1{<xliff:g id="COUNT_0">^1</xliff:g> element}few{<xliff:g id="COUNT_1">^1</xliff:g> elementy}many{<xliff:g id="COUNT_1">^1</xliff:g> elementów}other{<xliff:g id="COUNT_1">^1</xliff:g> elementu}}"</string>
@@ -160,4 +169,8 @@
<string name="dialog_error_message" msgid="5120432204743681606">"Spróbuj ponownie później. Zdjęcia będą dostępne po rozwiązaniu problemu."</string>
<string name="dialog_error_title" msgid="636349284077820636">"Nie można wczytać niektórych zdjęć"</string>
<string name="dialog_button_text" msgid="351366485240852280">"OK"</string>
+ <!-- no translation found for permlab_accessMediaOwnerPackageName (3849443148060165651) -->
+ <skip />
+ <!-- no translation found for permdesc_accessMediaOwnerPackageName (7381563109363105371) -->
+ <skip />
</resources>
diff --git a/res/values-pt-rBR/strings.xml b/res/values-pt-rBR/strings.xml
index 326fde2..923737e 100644
--- a/res/values-pt-rBR/strings.xml
+++ b/res/values-pt-rBR/strings.xml
@@ -66,11 +66,20 @@
<string name="picker_preview" msgid="6257414886055861039">"Visualização"</string>
<string name="picker_work_profile" msgid="2083221066869141576">"Mudar para Trabalho"</string>
<string name="picker_personal_profile" msgid="639484258397758406">"Mudar para Pessoal"</string>
+ <string name="picker_personal_profile_label" msgid="6189198163209597344">"Pessoal"</string>
<string name="picker_profile_admin_title" msgid="4172022376418293777">"Bloqueado pelo administrador"</string>
<string name="picker_profile_admin_msg_from_personal" msgid="1941639895084555723">"Não é permitido o acesso a dados de trabalho em um app pessoal"</string>
<string name="picker_profile_admin_msg_from_work" msgid="8048524337462790110">"Não é permitido o acesso a dados pessoais em um app de trabalho"</string>
+ <!-- no translation found for picker_profile_admin_msg (4060112887923255178) -->
+ <skip />
+ <!-- no translation found for picker_profile_switch_message (1133817927412489487) -->
+ <skip />
<string name="picker_profile_work_paused_title" msgid="382212880704235925">"Os apps de trabalho foram pausados"</string>
+ <!-- no translation found for picker_profile_paused_title (2079739512895529028) -->
+ <skip />
<string name="picker_profile_work_paused_msg" msgid="6321552322125246726">"Para abrir as fotos de trabalho, ative os apps de trabalho e tente novamente"</string>
+ <!-- no translation found for picker_profile_paused_msg (1215076898583993782) -->
+ <skip />
<string name="picker_privacy_message" msgid="9132700451027116817">"Este app só pode acessar as fotos que você selecionar"</string>
<string name="picker_header_permissions" msgid="675872774407768495">"Selecione fotos e vídeos que podem ser acessados por este app"</string>
<string name="picker_album_item_count" msgid="4420723302534177596">"{count,plural, =1{<xliff:g id="COUNT_0">^1</xliff:g> item}one{<xliff:g id="COUNT_1">^1</xliff:g> item}many{<xliff:g id="COUNT_1">^1</xliff:g> itens}other{<xliff:g id="COUNT_1">^1</xliff:g> itens}}"</string>
@@ -160,4 +169,8 @@
<string name="dialog_error_message" msgid="5120432204743681606">"Tente de novo mais tarde. Suas fotos vão ficar disponíveis assim que o problema for resolvido."</string>
<string name="dialog_error_title" msgid="636349284077820636">"Não é possível carregar algumas fotos"</string>
<string name="dialog_button_text" msgid="351366485240852280">"Entendi"</string>
+ <!-- no translation found for permlab_accessMediaOwnerPackageName (3849443148060165651) -->
+ <skip />
+ <!-- no translation found for permdesc_accessMediaOwnerPackageName (7381563109363105371) -->
+ <skip />
</resources>
diff --git a/res/values-pt-rPT/strings.xml b/res/values-pt-rPT/strings.xml
index dddf82c..78d73bd 100644
--- a/res/values-pt-rPT/strings.xml
+++ b/res/values-pt-rPT/strings.xml
@@ -66,11 +66,20 @@
<string name="picker_preview" msgid="6257414886055861039">"Pré-visualizar"</string>
<string name="picker_work_profile" msgid="2083221066869141576">"Mudar para trabalho"</string>
<string name="picker_personal_profile" msgid="639484258397758406">"Mudar para pessoal"</string>
+ <string name="picker_personal_profile_label" msgid="6189198163209597344">"Pessoal"</string>
<string name="picker_profile_admin_title" msgid="4172022376418293777">"Bloqueado pelo administrador"</string>
<string name="picker_profile_admin_msg_from_personal" msgid="1941639895084555723">"O acesso aos dados de trabalho a partir de uma app pessoal não é permitido"</string>
<string name="picker_profile_admin_msg_from_work" msgid="8048524337462790110">"O acesso aos dados pessoais a partir de uma app de trabalho não é permitido"</string>
+ <!-- no translation found for picker_profile_admin_msg (4060112887923255178) -->
+ <skip />
+ <!-- no translation found for picker_profile_switch_message (1133817927412489487) -->
+ <skip />
<string name="picker_profile_work_paused_title" msgid="382212880704235925">"As apps de trabalho estão em pausa"</string>
+ <!-- no translation found for picker_profile_paused_title (2079739512895529028) -->
+ <skip />
<string name="picker_profile_work_paused_msg" msgid="6321552322125246726">"Para abrir fotos do trabalho, ative as apps de trabalho e tente novamente"</string>
+ <!-- no translation found for picker_profile_paused_msg (1215076898583993782) -->
+ <skip />
<string name="picker_privacy_message" msgid="9132700451027116817">"Esta app só pode aceder às fotos que selecionar"</string>
<string name="picker_header_permissions" msgid="675872774407768495">"Selecione os vídeos e as fotos aos quais permite que esta app tenha acesso"</string>
<string name="picker_album_item_count" msgid="4420723302534177596">"{count,plural, =1{<xliff:g id="COUNT_0">^1</xliff:g> item}many{<xliff:g id="COUNT_1">^1</xliff:g> itens}other{<xliff:g id="COUNT_1">^1</xliff:g> itens}}"</string>
@@ -160,4 +169,8 @@
<string name="dialog_error_message" msgid="5120432204743681606">"Tente mais tarde. As suas fotos vão estar disponíveis quando o problema estiver resolvido."</string>
<string name="dialog_error_title" msgid="636349284077820636">"Não é possível carregar algumas fotos"</string>
<string name="dialog_button_text" msgid="351366485240852280">"OK"</string>
+ <!-- no translation found for permlab_accessMediaOwnerPackageName (3849443148060165651) -->
+ <skip />
+ <!-- no translation found for permdesc_accessMediaOwnerPackageName (7381563109363105371) -->
+ <skip />
</resources>
diff --git a/res/values-pt/strings.xml b/res/values-pt/strings.xml
index 326fde2..923737e 100644
--- a/res/values-pt/strings.xml
+++ b/res/values-pt/strings.xml
@@ -66,11 +66,20 @@
<string name="picker_preview" msgid="6257414886055861039">"Visualização"</string>
<string name="picker_work_profile" msgid="2083221066869141576">"Mudar para Trabalho"</string>
<string name="picker_personal_profile" msgid="639484258397758406">"Mudar para Pessoal"</string>
+ <string name="picker_personal_profile_label" msgid="6189198163209597344">"Pessoal"</string>
<string name="picker_profile_admin_title" msgid="4172022376418293777">"Bloqueado pelo administrador"</string>
<string name="picker_profile_admin_msg_from_personal" msgid="1941639895084555723">"Não é permitido o acesso a dados de trabalho em um app pessoal"</string>
<string name="picker_profile_admin_msg_from_work" msgid="8048524337462790110">"Não é permitido o acesso a dados pessoais em um app de trabalho"</string>
+ <!-- no translation found for picker_profile_admin_msg (4060112887923255178) -->
+ <skip />
+ <!-- no translation found for picker_profile_switch_message (1133817927412489487) -->
+ <skip />
<string name="picker_profile_work_paused_title" msgid="382212880704235925">"Os apps de trabalho foram pausados"</string>
+ <!-- no translation found for picker_profile_paused_title (2079739512895529028) -->
+ <skip />
<string name="picker_profile_work_paused_msg" msgid="6321552322125246726">"Para abrir as fotos de trabalho, ative os apps de trabalho e tente novamente"</string>
+ <!-- no translation found for picker_profile_paused_msg (1215076898583993782) -->
+ <skip />
<string name="picker_privacy_message" msgid="9132700451027116817">"Este app só pode acessar as fotos que você selecionar"</string>
<string name="picker_header_permissions" msgid="675872774407768495">"Selecione fotos e vídeos que podem ser acessados por este app"</string>
<string name="picker_album_item_count" msgid="4420723302534177596">"{count,plural, =1{<xliff:g id="COUNT_0">^1</xliff:g> item}one{<xliff:g id="COUNT_1">^1</xliff:g> item}many{<xliff:g id="COUNT_1">^1</xliff:g> itens}other{<xliff:g id="COUNT_1">^1</xliff:g> itens}}"</string>
@@ -160,4 +169,8 @@
<string name="dialog_error_message" msgid="5120432204743681606">"Tente de novo mais tarde. Suas fotos vão ficar disponíveis assim que o problema for resolvido."</string>
<string name="dialog_error_title" msgid="636349284077820636">"Não é possível carregar algumas fotos"</string>
<string name="dialog_button_text" msgid="351366485240852280">"Entendi"</string>
+ <!-- no translation found for permlab_accessMediaOwnerPackageName (3849443148060165651) -->
+ <skip />
+ <!-- no translation found for permdesc_accessMediaOwnerPackageName (7381563109363105371) -->
+ <skip />
</resources>
diff --git a/res/values-ro/strings.xml b/res/values-ro/strings.xml
index d5306a8..c6e1939 100644
--- a/res/values-ro/strings.xml
+++ b/res/values-ro/strings.xml
@@ -66,11 +66,20 @@
<string name="picker_preview" msgid="6257414886055861039">"Previzualizare"</string>
<string name="picker_work_profile" msgid="2083221066869141576">"Comută la serviciu"</string>
<string name="picker_personal_profile" msgid="639484258397758406">"Comută la personal"</string>
+ <string name="picker_personal_profile_label" msgid="6189198163209597344">"Personal"</string>
<string name="picker_profile_admin_title" msgid="4172022376418293777">"Blocat de administrator"</string>
<string name="picker_profile_admin_msg_from_personal" msgid="1941639895084555723">"Accesarea datelor de lucru dintr-o aplicație personală nu este permisă"</string>
<string name="picker_profile_admin_msg_from_work" msgid="8048524337462790110">"Accesarea datelor cu caracter personal dintr-o aplicație pentru lucru nu este permisă"</string>
+ <!-- no translation found for picker_profile_admin_msg (4060112887923255178) -->
+ <skip />
+ <!-- no translation found for picker_profile_switch_message (1133817927412489487) -->
+ <skip />
<string name="picker_profile_work_paused_title" msgid="382212880704235925">"Aplicațiile pentru lucru sunt întrerupte"</string>
+ <!-- no translation found for picker_profile_paused_title (2079739512895529028) -->
+ <skip />
<string name="picker_profile_work_paused_msg" msgid="6321552322125246726">"Ca să deschizi fotografiile de lucru, pornește aplicațiile pentru lucru și încearcă din nou"</string>
+ <!-- no translation found for picker_profile_paused_msg (1215076898583993782) -->
+ <skip />
<string name="picker_privacy_message" msgid="9132700451027116817">"Aplicația poate accesa doar fotografiile pe care le selectezi"</string>
<string name="picker_header_permissions" msgid="675872774407768495">"Selectează fotografiile și videoclipurile pe care le poate accesa această aplicație"</string>
<string name="picker_album_item_count" msgid="4420723302534177596">"{count,plural, =1{<xliff:g id="COUNT_0">^1</xliff:g> element}few{<xliff:g id="COUNT_1">^1</xliff:g> elemente}other{<xliff:g id="COUNT_1">^1</xliff:g> de elemente}}"</string>
@@ -160,4 +169,8 @@
<string name="dialog_error_message" msgid="5120432204743681606">"Încearcă din nou mai târziu. Fotografiile tale vor fi disponibile după ce se rezolvă problema."</string>
<string name="dialog_error_title" msgid="636349284077820636">"Unele fotografii nu pot fi încărcate"</string>
<string name="dialog_button_text" msgid="351366485240852280">"OK"</string>
+ <!-- no translation found for permlab_accessMediaOwnerPackageName (3849443148060165651) -->
+ <skip />
+ <!-- no translation found for permdesc_accessMediaOwnerPackageName (7381563109363105371) -->
+ <skip />
</resources>
diff --git a/res/values-ru/strings.xml b/res/values-ru/strings.xml
index 18d77ea..8a180a4 100644
--- a/res/values-ru/strings.xml
+++ b/res/values-ru/strings.xml
@@ -66,11 +66,20 @@
<string name="picker_preview" msgid="6257414886055861039">"Предварительный просмотр"</string>
<string name="picker_work_profile" msgid="2083221066869141576">"Перейти в рабочий профиль"</string>
<string name="picker_personal_profile" msgid="639484258397758406">"Перейти в личный профиль"</string>
+ <string name="picker_personal_profile_label" msgid="6189198163209597344">"Личный профиль"</string>
<string name="picker_profile_admin_title" msgid="4172022376418293777">"Заблокировано администратором"</string>
<string name="picker_profile_admin_msg_from_personal" msgid="1941639895084555723">"Доступ к рабочим данным из личного приложения запрещен."</string>
<string name="picker_profile_admin_msg_from_work" msgid="8048524337462790110">"Доступ к персональным данным из рабочего приложения запрещен."</string>
+ <!-- no translation found for picker_profile_admin_msg (4060112887923255178) -->
+ <skip />
+ <!-- no translation found for picker_profile_switch_message (1133817927412489487) -->
+ <skip />
<string name="picker_profile_work_paused_title" msgid="382212880704235925">"Рабочие приложения приостановлены"</string>
+ <!-- no translation found for picker_profile_paused_title (2079739512895529028) -->
+ <skip />
<string name="picker_profile_work_paused_msg" msgid="6321552322125246726">"Чтобы открыть рабочие фотографии, включите рабочие приложения и повторите попытку."</string>
+ <!-- no translation found for picker_profile_paused_msg (1215076898583993782) -->
+ <skip />
<string name="picker_privacy_message" msgid="9132700451027116817">"Этому приложению доступны только выбранные вами фото."</string>
<string name="picker_header_permissions" msgid="675872774407768495">"Выберите фотографии и видео, к которым у этого приложения будет доступ"</string>
<string name="picker_album_item_count" msgid="4420723302534177596">"{count,plural, =1{<xliff:g id="COUNT_0">^1</xliff:g> объект}one{<xliff:g id="COUNT_1">^1</xliff:g> объект}few{<xliff:g id="COUNT_1">^1</xliff:g> объекта}many{<xliff:g id="COUNT_1">^1</xliff:g> объектов}other{<xliff:g id="COUNT_1">^1</xliff:g> объекта}}"</string>
@@ -160,4 +169,8 @@
<string name="dialog_error_message" msgid="5120432204743681606">"Повторите попытку позже. Ваши фотографии станут доступны после устранения проблемы."</string>
<string name="dialog_error_title" msgid="636349284077820636">"Не удается загрузить некоторые фотографии"</string>
<string name="dialog_button_text" msgid="351366485240852280">"ОК"</string>
+ <!-- no translation found for permlab_accessMediaOwnerPackageName (3849443148060165651) -->
+ <skip />
+ <!-- no translation found for permdesc_accessMediaOwnerPackageName (7381563109363105371) -->
+ <skip />
</resources>
diff --git a/res/values-si/strings.xml b/res/values-si/strings.xml
index e7c1b3c..1ceeca1 100644
--- a/res/values-si/strings.xml
+++ b/res/values-si/strings.xml
@@ -66,11 +66,20 @@
<string name="picker_preview" msgid="6257414886055861039">"පෙරදසුන"</string>
<string name="picker_work_profile" msgid="2083221066869141576">"කාර්යාලය වෙත මාරු වන්න"</string>
<string name="picker_personal_profile" msgid="639484258397758406">"පුද්ගලික වෙත මාරු වන්න"</string>
+ <string name="picker_personal_profile_label" msgid="6189198163209597344">"පුද්ගලික"</string>
<string name="picker_profile_admin_title" msgid="4172022376418293777">"ඔබගේ පරිපාලක විසින් අවහිර කර ඇත"</string>
<string name="picker_profile_admin_msg_from_personal" msgid="1941639895084555723">"පුද්ගලික යෙදුමකින් කාර්යාල දත්තවලට ප්රවේශ වීමට අවසර නොදේ"</string>
<string name="picker_profile_admin_msg_from_work" msgid="8048524337462790110">"කාර්යාල යෙදුමකින් පුද්ගලික දත්තවලට ප්රවේශ වීමට අවසර නොදේ"</string>
+ <!-- no translation found for picker_profile_admin_msg (4060112887923255178) -->
+ <skip />
+ <!-- no translation found for picker_profile_switch_message (1133817927412489487) -->
+ <skip />
<string name="picker_profile_work_paused_title" msgid="382212880704235925">"කාර්යාල යෙදුම් විරාම කර ඇත"</string>
+ <!-- no translation found for picker_profile_paused_title (2079739512895529028) -->
+ <skip />
<string name="picker_profile_work_paused_msg" msgid="6321552322125246726">"කාර්යාල ඡායාරූප විවෘත කිරීමට, ඔබගේ කාර්යාල යෙදුම් ක්රියාත්මක කර නැවත උත්සාහ කරන්න"</string>
+ <!-- no translation found for picker_profile_paused_msg (1215076898583993782) -->
+ <skip />
<string name="picker_privacy_message" msgid="9132700451027116817">"මෙම යෙදුමට ප්රවේශ විය හැක්කේ ඔබ තෝරන ඡායාරූපවලට පමණි"</string>
<string name="picker_header_permissions" msgid="675872774407768495">"ඔබ මෙම යෙදුමට ප්රවේශ වීමට ඉඩ දෙන ඡායාරූප සහ වීඩියෝ තෝරන්න"</string>
<string name="picker_album_item_count" msgid="4420723302534177596">"{count,plural, =1{අයිතම <xliff:g id="COUNT_0">^1</xliff:g>}one{අයිතම <xliff:g id="COUNT_1">^1</xliff:g>}other{අයිතම <xliff:g id="COUNT_1">^1</xliff:g>}}"</string>
@@ -160,4 +169,8 @@
<string name="dialog_error_message" msgid="5120432204743681606">"පසුව නැවත උත්සාහ කරන්න. ගැටලුව විසඳූ පසු ඔබේ ඡායාරූප ලබා ගත හැකි වනු ඇත."</string>
<string name="dialog_error_title" msgid="636349284077820636">"සමහර ඡායාරූප පූරණය කළ නොහැක"</string>
<string name="dialog_button_text" msgid="351366485240852280">"තේරුණා"</string>
+ <!-- no translation found for permlab_accessMediaOwnerPackageName (3849443148060165651) -->
+ <skip />
+ <!-- no translation found for permdesc_accessMediaOwnerPackageName (7381563109363105371) -->
+ <skip />
</resources>
diff --git a/res/values-sk/strings.xml b/res/values-sk/strings.xml
index b77206a..46d62be 100644
--- a/res/values-sk/strings.xml
+++ b/res/values-sk/strings.xml
@@ -66,11 +66,20 @@
<string name="picker_preview" msgid="6257414886055861039">"Ukážka"</string>
<string name="picker_work_profile" msgid="2083221066869141576">"Prepnúť na pracovný profil"</string>
<string name="picker_personal_profile" msgid="639484258397758406">"Prepnúť na osobný profil"</string>
+ <string name="picker_personal_profile_label" msgid="6189198163209597344">"Osobný"</string>
<string name="picker_profile_admin_title" msgid="4172022376418293777">"Blokované vaším správcom"</string>
<string name="picker_profile_admin_msg_from_personal" msgid="1941639895084555723">"Prístup k pracovným údajom z osobnej aplikácie nie je povolený"</string>
<string name="picker_profile_admin_msg_from_work" msgid="8048524337462790110">"Prístup k osobným údajom z pracovnej aplikácie nie je povolený"</string>
+ <!-- no translation found for picker_profile_admin_msg (4060112887923255178) -->
+ <skip />
+ <!-- no translation found for picker_profile_switch_message (1133817927412489487) -->
+ <skip />
<string name="picker_profile_work_paused_title" msgid="382212880704235925">"Pracovné aplikácie sú pozastavené"</string>
+ <!-- no translation found for picker_profile_paused_title (2079739512895529028) -->
+ <skip />
<string name="picker_profile_work_paused_msg" msgid="6321552322125246726">"Ak chcete otvoriť pracovné fotky, zapnite pracovné aplikácie a skúste to znova"</string>
+ <!-- no translation found for picker_profile_paused_msg (1215076898583993782) -->
+ <skip />
<string name="picker_privacy_message" msgid="9132700451027116817">"Táto aplikácia má prístup iba k fotkám, ktoré vyberiete"</string>
<string name="picker_header_permissions" msgid="675872774407768495">"Vyberte fotky a videá, ku ktorým má mať táto aplikácia prístup"</string>
<string name="picker_album_item_count" msgid="4420723302534177596">"{count,plural, =1{<xliff:g id="COUNT_0">^1</xliff:g> položka}few{<xliff:g id="COUNT_1">^1</xliff:g> položky}many{<xliff:g id="COUNT_1">^1</xliff:g> items}other{<xliff:g id="COUNT_1">^1</xliff:g> položiek}}"</string>
@@ -160,4 +169,8 @@
<string name="dialog_error_message" msgid="5120432204743681606">"Skúste to neskôr. Po vyriešení problému budú vaše fotky k dispozícii."</string>
<string name="dialog_error_title" msgid="636349284077820636">"Niektoré fotky sa nedajú načítať"</string>
<string name="dialog_button_text" msgid="351366485240852280">"Dobre"</string>
+ <!-- no translation found for permlab_accessMediaOwnerPackageName (3849443148060165651) -->
+ <skip />
+ <!-- no translation found for permdesc_accessMediaOwnerPackageName (7381563109363105371) -->
+ <skip />
</resources>
diff --git a/res/values-sl/strings.xml b/res/values-sl/strings.xml
index 119ad24..9442524 100644
--- a/res/values-sl/strings.xml
+++ b/res/values-sl/strings.xml
@@ -66,11 +66,20 @@
<string name="picker_preview" msgid="6257414886055861039">"Predogled"</string>
<string name="picker_work_profile" msgid="2083221066869141576">"Preklop na delovni profil"</string>
<string name="picker_personal_profile" msgid="639484258397758406">"Preklop na osebni profil"</string>
+ <string name="picker_personal_profile_label" msgid="6189198163209597344">"Osebno"</string>
<string name="picker_profile_admin_title" msgid="4172022376418293777">"Blokiral skrbnik."</string>
<string name="picker_profile_admin_msg_from_personal" msgid="1941639895084555723">"Iz osebne aplikacije ni dovoljeno dostopati do delovnih podatkov."</string>
<string name="picker_profile_admin_msg_from_work" msgid="8048524337462790110">"Iz delovne aplikacije ni dovoljeno dostopati do osebnih podatkov."</string>
+ <!-- no translation found for picker_profile_admin_msg (4060112887923255178) -->
+ <skip />
+ <!-- no translation found for picker_profile_switch_message (1133817927412489487) -->
+ <skip />
<string name="picker_profile_work_paused_title" msgid="382212880704235925">"Delovne aplikacije so začasno zaustavljene"</string>
+ <!-- no translation found for picker_profile_paused_title (2079739512895529028) -->
+ <skip />
<string name="picker_profile_work_paused_msg" msgid="6321552322125246726">"Če želite odpreti delovne fotografije, vklopite delovne aplikacije in poskusite znova."</string>
+ <!-- no translation found for picker_profile_paused_msg (1215076898583993782) -->
+ <skip />
<string name="picker_privacy_message" msgid="9132700451027116817">"Ta aplikacija ima dostop samo do fotografij, ki jih izberete."</string>
<string name="picker_header_permissions" msgid="675872774407768495">"Izberite fotografije in videoposnetke, do katerih aplikaciji dovolite dostop."</string>
<string name="picker_album_item_count" msgid="4420723302534177596">"{count,plural, =1{<xliff:g id="COUNT_0">^1</xliff:g> element}one{<xliff:g id="COUNT_1">^1</xliff:g> element}two{<xliff:g id="COUNT_1">^1</xliff:g> elementa}few{<xliff:g id="COUNT_1">^1</xliff:g> elementi}other{<xliff:g id="COUNT_1">^1</xliff:g> elementov}}"</string>
@@ -160,4 +169,8 @@
<string name="dialog_error_message" msgid="5120432204743681606">"Poskusite znova pozneje. Fotografije bodo na voljo, ko bo težava odpravljena."</string>
<string name="dialog_error_title" msgid="636349284077820636">"Nekaterih fotografij ni mogoče naložiti"</string>
<string name="dialog_button_text" msgid="351366485240852280">"Razumem"</string>
+ <!-- no translation found for permlab_accessMediaOwnerPackageName (3849443148060165651) -->
+ <skip />
+ <!-- no translation found for permdesc_accessMediaOwnerPackageName (7381563109363105371) -->
+ <skip />
</resources>
diff --git a/res/values-sq/strings.xml b/res/values-sq/strings.xml
index a8e5c18..e183bf9 100644
--- a/res/values-sq/strings.xml
+++ b/res/values-sq/strings.xml
@@ -66,11 +66,20 @@
<string name="picker_preview" msgid="6257414886055861039">"Pamja paraprake"</string>
<string name="picker_work_profile" msgid="2083221066869141576">"Kalo te profili i punës"</string>
<string name="picker_personal_profile" msgid="639484258397758406">"Kalo te profili personal"</string>
+ <string name="picker_personal_profile_label" msgid="6189198163209597344">"Personal"</string>
<string name="picker_profile_admin_title" msgid="4172022376418293777">"Bllokuar nga administratori yt"</string>
<string name="picker_profile_admin_msg_from_personal" msgid="1941639895084555723">"Qasja e të dhënave të punës nga një aplikacion personal nuk lejohet"</string>
<string name="picker_profile_admin_msg_from_work" msgid="8048524337462790110">"Qasja e të dhënave personale nga një aplikacion pune nuk lejohet"</string>
+ <!-- no translation found for picker_profile_admin_msg (4060112887923255178) -->
+ <skip />
+ <!-- no translation found for picker_profile_switch_message (1133817927412489487) -->
+ <skip />
<string name="picker_profile_work_paused_title" msgid="382212880704235925">"Aplikacionet e punës janë vendosur në pauzë"</string>
+ <!-- no translation found for picker_profile_paused_title (2079739512895529028) -->
+ <skip />
<string name="picker_profile_work_paused_msg" msgid="6321552322125246726">"Për të hapur fotografitë e punës, aktivizo aplikacionet e punës dhe provo sërish"</string>
+ <!-- no translation found for picker_profile_paused_msg (1215076898583993782) -->
+ <skip />
<string name="picker_privacy_message" msgid="9132700451027116817">"Ky aplikacion mund të ketë qasje vetëm në fotografitë që zgjedh ti"</string>
<string name="picker_header_permissions" msgid="675872774407768495">"Zgjidh fotografitë dhe videot që lejon këtë aplikacion të ketë qasje"</string>
<string name="picker_album_item_count" msgid="4420723302534177596">"{count,plural, =1{<xliff:g id="COUNT_0">^1</xliff:g> artikull}other{<xliff:g id="COUNT_1">^1</xliff:g> artikuj}}"</string>
@@ -160,4 +169,8 @@
<string name="dialog_error_message" msgid="5120432204743681606">"Provo sërish më vonë. Fotografitë e tua do të ofrohen pasi të zgjidhet problemi."</string>
<string name="dialog_error_title" msgid="636349284077820636">"Disa fotografi nuk mund të ngarkohen"</string>
<string name="dialog_button_text" msgid="351366485240852280">"E kuptova"</string>
+ <!-- no translation found for permlab_accessMediaOwnerPackageName (3849443148060165651) -->
+ <skip />
+ <!-- no translation found for permdesc_accessMediaOwnerPackageName (7381563109363105371) -->
+ <skip />
</resources>
diff --git a/res/values-sr/strings.xml b/res/values-sr/strings.xml
index bda0c5f..f9b41fb 100644
--- a/res/values-sr/strings.xml
+++ b/res/values-sr/strings.xml
@@ -66,11 +66,20 @@
<string name="picker_preview" msgid="6257414886055861039">"Преглед"</string>
<string name="picker_work_profile" msgid="2083221066869141576">"Пређи на пословни профил"</string>
<string name="picker_personal_profile" msgid="639484258397758406">"Пређи на лични профил"</string>
+ <string name="picker_personal_profile_label" msgid="6189198163209597344">"Лично"</string>
<string name="picker_profile_admin_title" msgid="4172022376418293777">"Блокира администратор"</string>
<string name="picker_profile_admin_msg_from_personal" msgid="1941639895084555723">"Приступ подацима о послу из личне апликације није дозвољен"</string>
<string name="picker_profile_admin_msg_from_work" msgid="8048524337462790110">"Приступ личним подацима из пословне апликације није дозвољен"</string>
+ <!-- no translation found for picker_profile_admin_msg (4060112887923255178) -->
+ <skip />
+ <!-- no translation found for picker_profile_switch_message (1133817927412489487) -->
+ <skip />
<string name="picker_profile_work_paused_title" msgid="382212880704235925">"Пословне апликације су паузиране"</string>
+ <!-- no translation found for picker_profile_paused_title (2079739512895529028) -->
+ <skip />
<string name="picker_profile_work_paused_msg" msgid="6321552322125246726">"Да бисте отворили пословне слике, укључите пословне апликације, па пробајте поново"</string>
+ <!-- no translation found for picker_profile_paused_msg (1215076898583993782) -->
+ <skip />
<string name="picker_privacy_message" msgid="9132700451027116817">"Ова апликација може да приступа само сликама које изаберете"</string>
<string name="picker_header_permissions" msgid="675872774407768495">"Изаберите слике и видео снимке за које овој апликацији омогућавате приступ"</string>
<string name="picker_album_item_count" msgid="4420723302534177596">"{count,plural, =1{<xliff:g id="COUNT_0">^1</xliff:g> ставка}one{<xliff:g id="COUNT_1">^1</xliff:g> ставка}few{<xliff:g id="COUNT_1">^1</xliff:g> ставке}other{<xliff:g id="COUNT_1">^1</xliff:g> ставки}}"</string>
@@ -160,4 +169,8 @@
<string name="dialog_error_message" msgid="5120432204743681606">"Пробајте поново касније. Слике ће бити доступне када се проблем реши."</string>
<string name="dialog_error_title" msgid="636349284077820636">"Учитавање неких слика није успело"</string>
<string name="dialog_button_text" msgid="351366485240852280">"Важи"</string>
+ <!-- no translation found for permlab_accessMediaOwnerPackageName (3849443148060165651) -->
+ <skip />
+ <!-- no translation found for permdesc_accessMediaOwnerPackageName (7381563109363105371) -->
+ <skip />
</resources>
diff --git a/res/values-sv/strings.xml b/res/values-sv/strings.xml
index 4ebe034..9981722 100644
--- a/res/values-sv/strings.xml
+++ b/res/values-sv/strings.xml
@@ -66,11 +66,20 @@
<string name="picker_preview" msgid="6257414886055861039">"Förhandsgranska"</string>
<string name="picker_work_profile" msgid="2083221066869141576">"Byt till jobbprofilen"</string>
<string name="picker_personal_profile" msgid="639484258397758406">"Byt till den privata profilen"</string>
+ <string name="picker_personal_profile_label" msgid="6189198163209597344">"Personlig"</string>
<string name="picker_profile_admin_title" msgid="4172022376418293777">"Blockeras av administratören"</string>
<string name="picker_profile_admin_msg_from_personal" msgid="1941639895084555723">"Åtkomst till jobbdata från en privat app tillåts inte"</string>
<string name="picker_profile_admin_msg_from_work" msgid="8048524337462790110">"Åtkomst till privat data från en jobbapp tillåts inte"</string>
+ <!-- no translation found for picker_profile_admin_msg (4060112887923255178) -->
+ <skip />
+ <!-- no translation found for picker_profile_switch_message (1133817927412489487) -->
+ <skip />
<string name="picker_profile_work_paused_title" msgid="382212880704235925">"Jobbappar har pausats"</string>
+ <!-- no translation found for picker_profile_paused_title (2079739512895529028) -->
+ <skip />
<string name="picker_profile_work_paused_msg" msgid="6321552322125246726">"Om du vill öppna jobbfoton aktiverar du jobbappar och försöker igen"</string>
+ <!-- no translation found for picker_profile_paused_msg (1215076898583993782) -->
+ <skip />
<string name="picker_privacy_message" msgid="9132700451027116817">"Denna app får bara tillgång till foton du väljer"</string>
<string name="picker_header_permissions" msgid="675872774407768495">"Välj vilka foton och videor du vill ge appen åtkomst till"</string>
<string name="picker_album_item_count" msgid="4420723302534177596">"{count,plural, =1{<xliff:g id="COUNT_0">^1</xliff:g> objekt}other{<xliff:g id="COUNT_1">^1</xliff:g> objekt}}"</string>
@@ -160,4 +169,8 @@
<string name="dialog_error_message" msgid="5120432204743681606">"Försök igen senare. Dina foton blir tillgängliga när problemet har lösts."</string>
<string name="dialog_error_title" msgid="636349284077820636">"Det gick inte att läsa in vissa foton"</string>
<string name="dialog_button_text" msgid="351366485240852280">"OK"</string>
+ <!-- no translation found for permlab_accessMediaOwnerPackageName (3849443148060165651) -->
+ <skip />
+ <!-- no translation found for permdesc_accessMediaOwnerPackageName (7381563109363105371) -->
+ <skip />
</resources>
diff --git a/res/values-sw/strings.xml b/res/values-sw/strings.xml
index 67f8103..f433fec 100644
--- a/res/values-sw/strings.xml
+++ b/res/values-sw/strings.xml
@@ -66,11 +66,20 @@
<string name="picker_preview" msgid="6257414886055861039">"Onyesho la kukagua"</string>
<string name="picker_work_profile" msgid="2083221066869141576">"Badili utumie wasifu wa kazini"</string>
<string name="picker_personal_profile" msgid="639484258397758406">"Badili utumie wasifu wa binafsi"</string>
+ <string name="picker_personal_profile_label" msgid="6189198163209597344">"Binafsi"</string>
<string name="picker_profile_admin_title" msgid="4172022376418293777">"Umezuiwa na msimamizi wako"</string>
<string name="picker_profile_admin_msg_from_personal" msgid="1941639895084555723">"Huruhusiwi kufikia data ya kazini kwenye programu ya binafsi"</string>
<string name="picker_profile_admin_msg_from_work" msgid="8048524337462790110">"Huruhusiwi kufikia data binafsi kwenye programu ya kazini"</string>
+ <!-- no translation found for picker_profile_admin_msg (4060112887923255178) -->
+ <skip />
+ <!-- no translation found for picker_profile_switch_message (1133817927412489487) -->
+ <skip />
<string name="picker_profile_work_paused_title" msgid="382212880704235925">"Programu za kazini zimesimamishwa"</string>
+ <!-- no translation found for picker_profile_paused_title (2079739512895529028) -->
+ <skip />
<string name="picker_profile_work_paused_msg" msgid="6321552322125246726">"Ili ufungue picha za kazini, washa programu zako za kazini kisha ujaribu tena"</string>
+ <!-- no translation found for picker_profile_paused_msg (1215076898583993782) -->
+ <skip />
<string name="picker_privacy_message" msgid="9132700451027116817">"Programu hii inaweza tu kufikia picha unazochagua"</string>
<string name="picker_header_permissions" msgid="675872774407768495">"Chagua picha na video unazoiruhusu programu hii kufikia"</string>
<string name="picker_album_item_count" msgid="4420723302534177596">"{count,plural, =1{Kipengee <xliff:g id="COUNT_0">^1</xliff:g>}other{Vipengee <xliff:g id="COUNT_1">^1</xliff:g>}}"</string>
@@ -160,4 +169,8 @@
<string name="dialog_error_message" msgid="5120432204743681606">"Jaribu tena baadaye. Picha zako zitapatikana mara tu tatizo litakapotatuliwa."</string>
<string name="dialog_error_title" msgid="636349284077820636">"Imeshindwa kupakia baadhi ya Picha"</string>
<string name="dialog_button_text" msgid="351366485240852280">"Nimeelewa"</string>
+ <!-- no translation found for permlab_accessMediaOwnerPackageName (3849443148060165651) -->
+ <skip />
+ <!-- no translation found for permdesc_accessMediaOwnerPackageName (7381563109363105371) -->
+ <skip />
</resources>
diff --git a/res/values-ta/strings.xml b/res/values-ta/strings.xml
index d53729d..c4505ad 100644
--- a/res/values-ta/strings.xml
+++ b/res/values-ta/strings.xml
@@ -66,11 +66,20 @@
<string name="picker_preview" msgid="6257414886055861039">"மாதிரிக்காட்சி"</string>
<string name="picker_work_profile" msgid="2083221066869141576">"பணிச் சுயவிவரத்திற்கு மாறு"</string>
<string name="picker_personal_profile" msgid="639484258397758406">"தனிப்பட்ட சுயவிவரத்திற்கு மாறு"</string>
+ <string name="picker_personal_profile_label" msgid="6189198163209597344">"தனிப்பட்டவை"</string>
<string name="picker_profile_admin_title" msgid="4172022376418293777">"உங்கள் நிர்வாகி தடைசெய்துள்ளார்"</string>
<string name="picker_profile_admin_msg_from_personal" msgid="1941639895084555723">"தனிப்பட்ட ஆப்ஸிலிருந்து பணித் தரவை அணுக அனுமதியில்லை"</string>
<string name="picker_profile_admin_msg_from_work" msgid="8048524337462790110">"பணி ஆப்ஸிலிருந்து தனிப்பட்ட தரவை அணுக அனுமதியில்லை"</string>
+ <!-- no translation found for picker_profile_admin_msg (4060112887923255178) -->
+ <skip />
+ <!-- no translation found for picker_profile_switch_message (1133817927412489487) -->
+ <skip />
<string name="picker_profile_work_paused_title" msgid="382212880704235925">"பணி ஆப்ஸ் இடைநிறுத்தப்பட்டுள்ளன"</string>
+ <!-- no translation found for picker_profile_paused_title (2079739512895529028) -->
+ <skip />
<string name="picker_profile_work_paused_msg" msgid="6321552322125246726">"பணிப் படங்களைத் திறக்க, பணி ஆப்ஸை ஆன் செய்துவிட்டு முயலவும்"</string>
+ <!-- no translation found for picker_profile_paused_msg (1215076898583993782) -->
+ <skip />
<string name="picker_privacy_message" msgid="9132700451027116817">"நீங்கள் தேர்ந்தெடுக்கும் படங்களை மட்டுமே இந்த ஆப்ஸ் அணுக முடியும்"</string>
<string name="picker_header_permissions" msgid="675872774407768495">"இந்த ஆப்ஸ் அணுகுவதற்கு நீங்கள் அனுமதிக்கும் படங்களையும் வீடியோக்களையும் தேர்ந்தெடுங்கள்"</string>
<string name="picker_album_item_count" msgid="4420723302534177596">"{count,plural, =1{<xliff:g id="COUNT_0">^1</xliff:g> ஆவணம்}other{<xliff:g id="COUNT_1">^1</xliff:g> ஆவணங்கள்}}"</string>
@@ -160,4 +169,8 @@
<string name="dialog_error_message" msgid="5120432204743681606">"பிறகு மீண்டும் முயலவும். சிக்கல் சரியானதும் உங்கள் படங்கள் கிடைக்கும்."</string>
<string name="dialog_error_title" msgid="636349284077820636">"சில படங்களை ஏற்ற முடியவில்லை"</string>
<string name="dialog_button_text" msgid="351366485240852280">"சரி"</string>
+ <!-- no translation found for permlab_accessMediaOwnerPackageName (3849443148060165651) -->
+ <skip />
+ <!-- no translation found for permdesc_accessMediaOwnerPackageName (7381563109363105371) -->
+ <skip />
</resources>
diff --git a/res/values-te/strings.xml b/res/values-te/strings.xml
index 3e8469b..0ec4b8c 100644
--- a/res/values-te/strings.xml
+++ b/res/values-te/strings.xml
@@ -66,11 +66,20 @@
<string name="picker_preview" msgid="6257414886055861039">"ప్రివ్యూ"</string>
<string name="picker_work_profile" msgid="2083221066869141576">"వర్క్ ప్రొఫైల్కు మార్చండి"</string>
<string name="picker_personal_profile" msgid="639484258397758406">"వ్యక్తిగత ప్రొఫైల్కు మార్చండి"</string>
+ <string name="picker_personal_profile_label" msgid="6189198163209597344">"వ్యక్తిగతం"</string>
<string name="picker_profile_admin_title" msgid="4172022376418293777">"మీ అడ్మిన్ బ్లాక్ చేశారు"</string>
<string name="picker_profile_admin_msg_from_personal" msgid="1941639895084555723">"వ్యక్తిగత యాప్ నుండి వర్క్ డేటాను యాక్సెస్ చేయడం అనుమతించబడదు"</string>
<string name="picker_profile_admin_msg_from_work" msgid="8048524337462790110">"వర్క్ యాప్ నుండి వ్యక్తిగత డేటాను యాక్సెస్ చేయడం అనుమతించబడదు"</string>
+ <!-- no translation found for picker_profile_admin_msg (4060112887923255178) -->
+ <skip />
+ <!-- no translation found for picker_profile_switch_message (1133817927412489487) -->
+ <skip />
<string name="picker_profile_work_paused_title" msgid="382212880704235925">"వర్క్ యాప్లు పాజ్ చేయబడ్డాయి"</string>
+ <!-- no translation found for picker_profile_paused_title (2079739512895529028) -->
+ <skip />
<string name="picker_profile_work_paused_msg" msgid="6321552322125246726">"వర్క్ ఫోటోలను తెరవడానికి, మీ వర్క్ యాప్లను ఆన్ చేసి, ఆపై మళ్లీ ట్రై చేయండి"</string>
+ <!-- no translation found for picker_profile_paused_msg (1215076898583993782) -->
+ <skip />
<string name="picker_privacy_message" msgid="9132700451027116817">"ఈ యాప్ మీరు ఎంచుకున్న ఫోటోలను మాత్రమే యాక్సెస్ చేయగలదు"</string>
<string name="picker_header_permissions" msgid="675872774407768495">"ఈ యాప్, యాక్సెస్ చేయడానికి మీరు అనుమతించే ఫోటోలను, వీడియోలను ఎంచుకోండి"</string>
<string name="picker_album_item_count" msgid="4420723302534177596">"{count,plural, =1{<xliff:g id="COUNT_0">^1</xliff:g> ఐటెమ్}other{<xliff:g id="COUNT_1">^1</xliff:g> ఐటెమ్లు}}"</string>
@@ -160,4 +169,8 @@
<string name="dialog_error_message" msgid="5120432204743681606">"తర్వాత మళ్లీ ట్రై చేయండి. సమస్య పరిష్కరించబడిన తర్వాత మీ ఫోటోలు అందుబాటులో ఉంటాయి."</string>
<string name="dialog_error_title" msgid="636349284077820636">"కొన్ని ఫోటోలను లోడ్ చేయడం సాధ్యపడదు"</string>
<string name="dialog_button_text" msgid="351366485240852280">"సరే"</string>
+ <!-- no translation found for permlab_accessMediaOwnerPackageName (3849443148060165651) -->
+ <skip />
+ <!-- no translation found for permdesc_accessMediaOwnerPackageName (7381563109363105371) -->
+ <skip />
</resources>
diff --git a/res/values-th/strings.xml b/res/values-th/strings.xml
index a2afd2a..60fcdef 100644
--- a/res/values-th/strings.xml
+++ b/res/values-th/strings.xml
@@ -66,11 +66,20 @@
<string name="picker_preview" msgid="6257414886055861039">"ตัวอย่าง"</string>
<string name="picker_work_profile" msgid="2083221066869141576">"เปลี่ยนไปใช้โปรไฟล์งาน"</string>
<string name="picker_personal_profile" msgid="639484258397758406">"เปลี่ยนไปใช้โปรไฟล์ส่วนตัว"</string>
+ <string name="picker_personal_profile_label" msgid="6189198163209597344">"ส่วนตัว"</string>
<string name="picker_profile_admin_title" msgid="4172022376418293777">"ผู้ดูแลระบบบล็อกไว้"</string>
<string name="picker_profile_admin_msg_from_personal" msgid="1941639895084555723">"ไม่อนุญาตให้เข้าถึงข้อมูลงานจากแอปส่วนตัว"</string>
<string name="picker_profile_admin_msg_from_work" msgid="8048524337462790110">"ไม่อนุญาตให้เข้าถึงข้อมูลส่วนตัวจากแอปงาน"</string>
+ <!-- no translation found for picker_profile_admin_msg (4060112887923255178) -->
+ <skip />
+ <!-- no translation found for picker_profile_switch_message (1133817927412489487) -->
+ <skip />
<string name="picker_profile_work_paused_title" msgid="382212880704235925">"แอปงานหยุดชั่วคราว"</string>
+ <!-- no translation found for picker_profile_paused_title (2079739512895529028) -->
+ <skip />
<string name="picker_profile_work_paused_msg" msgid="6321552322125246726">"หากต้องการเปิดรูปภาพงาน ให้เปิดแอปงานแล้วลองอีกครั้ง"</string>
+ <!-- no translation found for picker_profile_paused_msg (1215076898583993782) -->
+ <skip />
<string name="picker_privacy_message" msgid="9132700451027116817">"แอปนี้จะเข้าถึงได้เฉพาะรูปภาพที่คุณเลือกเท่านั้น"</string>
<string name="picker_header_permissions" msgid="675872774407768495">"เลือกรูปภาพและวิดีโอที่จะอนุญาตให้แอปนี้เข้าถึงได้"</string>
<string name="picker_album_item_count" msgid="4420723302534177596">"{count,plural, =1{<xliff:g id="COUNT_0">^1</xliff:g> รายการ}other{<xliff:g id="COUNT_1">^1</xliff:g> รายการ}}"</string>
@@ -160,4 +169,8 @@
<string name="dialog_error_message" msgid="5120432204743681606">"โปรดลองอีกครั้งในภายหลัง รูปภาพจะพร้อมใช้งานเมื่อปัญหาได้รับการแก้ไขแล้ว"</string>
<string name="dialog_error_title" msgid="636349284077820636">"โหลดรูปภาพบางรูปไม่ได้"</string>
<string name="dialog_button_text" msgid="351366485240852280">"รับทราบ"</string>
+ <!-- no translation found for permlab_accessMediaOwnerPackageName (3849443148060165651) -->
+ <skip />
+ <!-- no translation found for permdesc_accessMediaOwnerPackageName (7381563109363105371) -->
+ <skip />
</resources>
diff --git a/res/values-tl/strings.xml b/res/values-tl/strings.xml
index 473a335..78c6073 100644
--- a/res/values-tl/strings.xml
+++ b/res/values-tl/strings.xml
@@ -66,11 +66,20 @@
<string name="picker_preview" msgid="6257414886055861039">"Preview"</string>
<string name="picker_work_profile" msgid="2083221066869141576">"Lumipat sa para sa trabaho"</string>
<string name="picker_personal_profile" msgid="639484258397758406">"Lumipat sa personal"</string>
+ <string name="picker_personal_profile_label" msgid="6189198163209597344">"Personal"</string>
<string name="picker_profile_admin_title" msgid="4172022376418293777">"Na-block ng iyong admin"</string>
<string name="picker_profile_admin_msg_from_personal" msgid="1941639895084555723">"Hindi pinapahintulutan ang pag-access ng data ng trabaho mula sa isang personal na app"</string>
<string name="picker_profile_admin_msg_from_work" msgid="8048524337462790110">"Hindi pinapahintulutan ang pag-access ng personal na data mula sa isang app para sa trabaho"</string>
+ <!-- no translation found for picker_profile_admin_msg (4060112887923255178) -->
+ <skip />
+ <!-- no translation found for picker_profile_switch_message (1133817927412489487) -->
+ <skip />
<string name="picker_profile_work_paused_title" msgid="382212880704235925">"Naka-pause ang mga app para sa trabaho"</string>
+ <!-- no translation found for picker_profile_paused_title (2079739512895529028) -->
+ <skip />
<string name="picker_profile_work_paused_msg" msgid="6321552322125246726">"Para buksan ang mga larawan sa trabaho, i-on ang iyong mga app sa trabaho at subukan ulit"</string>
+ <!-- no translation found for picker_profile_paused_msg (1215076898583993782) -->
+ <skip />
<string name="picker_privacy_message" msgid="9132700451027116817">"Ang mga larawang pipiliin mo lang ang maa-access ng app na ito"</string>
<string name="picker_header_permissions" msgid="675872774407768495">"Pumili ng mga larawan at video na papayagan mong ma-access ng app na ito"</string>
<string name="picker_album_item_count" msgid="4420723302534177596">"{count,plural, =1{<xliff:g id="COUNT_0">^1</xliff:g> item}one{<xliff:g id="COUNT_1">^1</xliff:g> item}other{<xliff:g id="COUNT_1">^1</xliff:g> na item}}"</string>
@@ -160,4 +169,6 @@
<string name="dialog_error_message" msgid="5120432204743681606">"Subukan ulit sa ibang pagkakataon. Magiging available ang iyong mga larawan kapag nalutas na ang isyu."</string>
<string name="dialog_error_title" msgid="636349284077820636">"Hindi ma-load ang ilang Larawan"</string>
<string name="dialog_button_text" msgid="351366485240852280">"OK"</string>
+ <string name="permlab_accessMediaOwnerPackageName" msgid="3849443148060165651">"I-access ang pangalan ng package ng may-ari ng media"</string>
+ <string name="permdesc_accessMediaOwnerPackageName" msgid="7381563109363105371">"Pinapayagan ang app na i-access ang mga pangalan ng package ng may-ari ng lahat ng naa-access na file ng media."</string>
</resources>
diff --git a/res/values-tr/strings.xml b/res/values-tr/strings.xml
index 037d32f..4cc4566 100644
--- a/res/values-tr/strings.xml
+++ b/res/values-tr/strings.xml
@@ -66,11 +66,20 @@
<string name="picker_preview" msgid="6257414886055861039">"Önizle"</string>
<string name="picker_work_profile" msgid="2083221066869141576">"İş profiline geç"</string>
<string name="picker_personal_profile" msgid="639484258397758406">"Kişisel profile geç"</string>
+ <string name="picker_personal_profile_label" msgid="6189198163209597344">"Kişisel"</string>
<string name="picker_profile_admin_title" msgid="4172022376418293777">"Yöneticiniz tarafından engellendi"</string>
<string name="picker_profile_admin_msg_from_personal" msgid="1941639895084555723">"Kişisel uygulamadan iş verilerine erişmeye izin verilmiyor"</string>
<string name="picker_profile_admin_msg_from_work" msgid="8048524337462790110">"İş uygulamasından kişisel verilere erişmeye izin verilmiyor"</string>
+ <!-- no translation found for picker_profile_admin_msg (4060112887923255178) -->
+ <skip />
+ <!-- no translation found for picker_profile_switch_message (1133817927412489487) -->
+ <skip />
<string name="picker_profile_work_paused_title" msgid="382212880704235925">"İş uygulamaları duraklatıldı"</string>
+ <!-- no translation found for picker_profile_paused_title (2079739512895529028) -->
+ <skip />
<string name="picker_profile_work_paused_msg" msgid="6321552322125246726">"İş fotoğraflarını açmak için iş uygulamalarını açıp tekrar deneyin"</string>
+ <!-- no translation found for picker_profile_paused_msg (1215076898583993782) -->
+ <skip />
<string name="picker_privacy_message" msgid="9132700451027116817">"Bu uygulama yalnızca seçtiğiniz fotoğraflara erişebilir"</string>
<string name="picker_header_permissions" msgid="675872774407768495">"Bu uygulamanın erişmesine izin verdiğiniz fotoğrafları ve videoları seçin"</string>
<string name="picker_album_item_count" msgid="4420723302534177596">"{count,plural, =1{<xliff:g id="COUNT_0">^1</xliff:g> öğe}other{<xliff:g id="COUNT_1">^1</xliff:g> öğe}}"</string>
@@ -160,4 +169,8 @@
<string name="dialog_error_message" msgid="5120432204743681606">"Daha sonra tekrar deneyin. Fotoğraflarınız, sorun çözüldükten sonra kullanılabilir."</string>
<string name="dialog_error_title" msgid="636349284077820636">"Bazı fotoğraflar yüklenemiyor"</string>
<string name="dialog_button_text" msgid="351366485240852280">"Anladım"</string>
+ <!-- no translation found for permlab_accessMediaOwnerPackageName (3849443148060165651) -->
+ <skip />
+ <!-- no translation found for permdesc_accessMediaOwnerPackageName (7381563109363105371) -->
+ <skip />
</resources>
diff --git a/res/values-uk/strings.xml b/res/values-uk/strings.xml
index b2b3947..710f7a7 100644
--- a/res/values-uk/strings.xml
+++ b/res/values-uk/strings.xml
@@ -66,11 +66,20 @@
<string name="picker_preview" msgid="6257414886055861039">"Попередній перегляд"</string>
<string name="picker_work_profile" msgid="2083221066869141576">"Перейти в робочий профіль"</string>
<string name="picker_personal_profile" msgid="639484258397758406">"Перейти в особистий профіль"</string>
+ <string name="picker_personal_profile_label" msgid="6189198163209597344">"Особистий"</string>
<string name="picker_profile_admin_title" msgid="4172022376418293777">"Заблоковано адміністратором"</string>
<string name="picker_profile_admin_msg_from_personal" msgid="1941639895084555723">"Доступ до робочих даних з особистого додатка заблокований"</string>
<string name="picker_profile_admin_msg_from_work" msgid="8048524337462790110">"Доступ до персональних даних із робочого додатка заблокований"</string>
+ <!-- no translation found for picker_profile_admin_msg (4060112887923255178) -->
+ <skip />
+ <!-- no translation found for picker_profile_switch_message (1133817927412489487) -->
+ <skip />
<string name="picker_profile_work_paused_title" msgid="382212880704235925">"Робочі додатки призупинено"</string>
+ <!-- no translation found for picker_profile_paused_title (2079739512895529028) -->
+ <skip />
<string name="picker_profile_work_paused_msg" msgid="6321552322125246726">"Щоб відкрити робочі фотографії, увімкніть робочі додатки й повторіть спробу"</string>
+ <!-- no translation found for picker_profile_paused_msg (1215076898583993782) -->
+ <skip />
<string name="picker_privacy_message" msgid="9132700451027116817">"Цей додаток має доступ лише до вибраних вами фотографій"</string>
<string name="picker_header_permissions" msgid="675872774407768495">"Виберіть фотографії та відео, до яких цей додаток матиме доступ"</string>
<string name="picker_album_item_count" msgid="4420723302534177596">"{count,plural, =1{<xliff:g id="COUNT_0">^1</xliff:g> об’єкт}one{<xliff:g id="COUNT_1">^1</xliff:g> об’єкт}few{<xliff:g id="COUNT_1">^1</xliff:g> об’єкти}many{<xliff:g id="COUNT_1">^1</xliff:g> об’єктів}other{<xliff:g id="COUNT_1">^1</xliff:g> об’єкта}}"</string>
@@ -160,4 +169,8 @@
<string name="dialog_error_message" msgid="5120432204743681606">"Повторіть спробу пізніше. Ваші фотографії будуть доступні після вирішення проблеми."</string>
<string name="dialog_error_title" msgid="636349284077820636">"Не вдається завантажити деякі фотографії"</string>
<string name="dialog_button_text" msgid="351366485240852280">"OK"</string>
+ <!-- no translation found for permlab_accessMediaOwnerPackageName (3849443148060165651) -->
+ <skip />
+ <!-- no translation found for permdesc_accessMediaOwnerPackageName (7381563109363105371) -->
+ <skip />
</resources>
diff --git a/res/values-ur/strings.xml b/res/values-ur/strings.xml
index dc215af..2d6f0f3 100644
--- a/res/values-ur/strings.xml
+++ b/res/values-ur/strings.xml
@@ -66,11 +66,20 @@
<string name="picker_preview" msgid="6257414886055861039">"پیش منظر"</string>
<string name="picker_work_profile" msgid="2083221066869141576">"کام پر سوئچ کریں"</string>
<string name="picker_personal_profile" msgid="639484258397758406">"ذاتی پروفائل پر سوئچ کریں"</string>
+ <string name="picker_personal_profile_label" msgid="6189198163209597344">"ذاتی"</string>
<string name="picker_profile_admin_title" msgid="4172022376418293777">"آپ کے منتظم کے ذریعے مسدود کردہ ہے"</string>
<string name="picker_profile_admin_msg_from_personal" msgid="1941639895084555723">"ذاتی ایپ سے دفتری ڈیٹا تک رسائی کی اجازت نہیں ہے"</string>
<string name="picker_profile_admin_msg_from_work" msgid="8048524337462790110">"ورک ایپ سے ذاتی ڈیٹا تک رسائی کی اجازت نہیں ہے"</string>
+ <!-- no translation found for picker_profile_admin_msg (4060112887923255178) -->
+ <skip />
+ <!-- no translation found for picker_profile_switch_message (1133817927412489487) -->
+ <skip />
<string name="picker_profile_work_paused_title" msgid="382212880704235925">"ورک ایپس موقوف ہیں"</string>
+ <!-- no translation found for picker_profile_paused_title (2079739512895529028) -->
+ <skip />
<string name="picker_profile_work_paused_msg" msgid="6321552322125246726">"کام کی تصاویر کھولنے کے لیے اپنی ورک ایپس آن کریں، پھر دوبارہ کوشش کریں"</string>
+ <!-- no translation found for picker_profile_paused_msg (1215076898583993782) -->
+ <skip />
<string name="picker_privacy_message" msgid="9132700451027116817">"یہ ایپ صرف آپ کی منتخب کردہ تصاویر تک رسائی حاصل کر سکتی ہے"</string>
<string name="picker_header_permissions" msgid="675872774407768495">"وہ تصاویر اور ویڈیوز منتخب کریں جن تک آپ اس ایپ کو رسائی کی اجازت دیتے ہیں"</string>
<string name="picker_album_item_count" msgid="4420723302534177596">"{count,plural, =1{<xliff:g id="COUNT_0">^1</xliff:g> آئٹم}other{<xliff:g id="COUNT_1">^1</xliff:g> آئٹمز}}"</string>
@@ -160,4 +169,8 @@
<string name="dialog_error_message" msgid="5120432204743681606">"بعد میں دوبارہ کوشش کریں۔ مسئلہ حل ہو جانے کے بعد آپ کی تصاویر دستیاب ہوں گی۔"</string>
<string name="dialog_error_title" msgid="636349284077820636">"کچھ تصاویر لوڈ نہیں کی جا سکتیں"</string>
<string name="dialog_button_text" msgid="351366485240852280">"سمجھ آ گئی"</string>
+ <!-- no translation found for permlab_accessMediaOwnerPackageName (3849443148060165651) -->
+ <skip />
+ <!-- no translation found for permdesc_accessMediaOwnerPackageName (7381563109363105371) -->
+ <skip />
</resources>
diff --git a/res/values-uz/strings.xml b/res/values-uz/strings.xml
index ac34311..7255ecd 100644
--- a/res/values-uz/strings.xml
+++ b/res/values-uz/strings.xml
@@ -66,11 +66,20 @@
<string name="picker_preview" msgid="6257414886055861039">"Razm solish"</string>
<string name="picker_work_profile" msgid="2083221066869141576">"Ish profiliga almashish"</string>
<string name="picker_personal_profile" msgid="639484258397758406">"Shaxsiy profilga almashish"</string>
+ <string name="picker_personal_profile_label" msgid="6189198163209597344">"Shaxsiy"</string>
<string name="picker_profile_admin_title" msgid="4172022376418293777">"Administratoringiz tomonidan bloklangan"</string>
<string name="picker_profile_admin_msg_from_personal" msgid="1941639895084555723">"Shaxsiy ilovadan ishga oid maʼlumotlarga kirish taqiqlangan"</string>
<string name="picker_profile_admin_msg_from_work" msgid="8048524337462790110">"Ishga oid ilovadan shaxsiy maʼlumotlarga kirish taqiqlangan"</string>
+ <!-- no translation found for picker_profile_admin_msg (4060112887923255178) -->
+ <skip />
+ <!-- no translation found for picker_profile_switch_message (1133817927412489487) -->
+ <skip />
<string name="picker_profile_work_paused_title" msgid="382212880704235925">"Ishga oid ilovalar pauza qilingan"</string>
+ <!-- no translation found for picker_profile_paused_title (2079739512895529028) -->
+ <skip />
<string name="picker_profile_work_paused_msg" msgid="6321552322125246726">"Ishga oid suratlarni ochish uchun ishga oid ilovalarni yoqib, keyin qaytadan urining"</string>
+ <!-- no translation found for picker_profile_paused_msg (1215076898583993782) -->
+ <skip />
<string name="picker_privacy_message" msgid="9132700451027116817">"Bu ilova faqat siz tanlagan suratlar bilan ishlay oladi"</string>
<string name="picker_header_permissions" msgid="675872774407768495">"Bu ilova kira oladigan surat va videolarni tanlang"</string>
<string name="picker_album_item_count" msgid="4420723302534177596">"{count,plural, =1{<xliff:g id="COUNT_0">^1</xliff:g> ta narsa}other{<xliff:g id="COUNT_1">^1</xliff:g> ta narsa}}"</string>
@@ -160,4 +169,8 @@
<string name="dialog_error_message" msgid="5120432204743681606">"Keyinroq qayta urining. Suratlaringiz muammo hal boʻlgandan keyin chiqadi."</string>
<string name="dialog_error_title" msgid="636349284077820636">"Ayrim suratlar yuklanmadi"</string>
<string name="dialog_button_text" msgid="351366485240852280">"OK"</string>
+ <!-- no translation found for permlab_accessMediaOwnerPackageName (3849443148060165651) -->
+ <skip />
+ <!-- no translation found for permdesc_accessMediaOwnerPackageName (7381563109363105371) -->
+ <skip />
</resources>
diff --git a/res/values-vi/strings.xml b/res/values-vi/strings.xml
index ae0b4e5..a00adf4 100644
--- a/res/values-vi/strings.xml
+++ b/res/values-vi/strings.xml
@@ -66,11 +66,20 @@
<string name="picker_preview" msgid="6257414886055861039">"Xem trước"</string>
<string name="picker_work_profile" msgid="2083221066869141576">"Chuyển sang hồ sơ công việc"</string>
<string name="picker_personal_profile" msgid="639484258397758406">"Chuyển sang hồ sơ cá nhân"</string>
+ <string name="picker_personal_profile_label" msgid="6189198163209597344">"Cá nhân"</string>
<string name="picker_profile_admin_title" msgid="4172022376418293777">"Quản trị viên của bạn đã chặn thao tác này"</string>
<string name="picker_profile_admin_msg_from_personal" msgid="1941639895084555723">"Bạn không được phép truy cập dữ liệu công việc từ một ứng dụng cá nhân"</string>
<string name="picker_profile_admin_msg_from_work" msgid="8048524337462790110">"Bạn không được phép truy cập dữ liệu cá nhân từ một ứng dụng công việc"</string>
+ <!-- no translation found for picker_profile_admin_msg (4060112887923255178) -->
+ <skip />
+ <!-- no translation found for picker_profile_switch_message (1133817927412489487) -->
+ <skip />
<string name="picker_profile_work_paused_title" msgid="382212880704235925">"Ứng dụng công việc đã tạm dừng"</string>
+ <!-- no translation found for picker_profile_paused_title (2079739512895529028) -->
+ <skip />
<string name="picker_profile_work_paused_msg" msgid="6321552322125246726">"Để mở ảnh trong hồ sơ công việc, hãy bật ứng dụng công việc rồi thử lại"</string>
+ <!-- no translation found for picker_profile_paused_msg (1215076898583993782) -->
+ <skip />
<string name="picker_privacy_message" msgid="9132700451027116817">"Ứng dụng này chỉ có thể truy cập vào những bức ảnh bạn chọn"</string>
<string name="picker_header_permissions" msgid="675872774407768495">"Chọn ảnh và video mà bạn cho phép ứng dụng này truy cập"</string>
<string name="picker_album_item_count" msgid="4420723302534177596">"{count,plural, =1{<xliff:g id="COUNT_0">^1</xliff:g> mục}other{<xliff:g id="COUNT_1">^1</xliff:g> mục}}"</string>
@@ -160,4 +169,8 @@
<string name="dialog_error_message" msgid="5120432204743681606">"Hãy thử lại sau. Ảnh của bạn sẽ xuất hiện sau khi vấn đề được giải quyết."</string>
<string name="dialog_error_title" msgid="636349284077820636">"Không tải được một số ảnh"</string>
<string name="dialog_button_text" msgid="351366485240852280">"Tôi hiểu"</string>
+ <!-- no translation found for permlab_accessMediaOwnerPackageName (3849443148060165651) -->
+ <skip />
+ <!-- no translation found for permdesc_accessMediaOwnerPackageName (7381563109363105371) -->
+ <skip />
</resources>
diff --git a/res/values-zh-rCN/strings.xml b/res/values-zh-rCN/strings.xml
index a2c02a2..3ebdfc1 100644
--- a/res/values-zh-rCN/strings.xml
+++ b/res/values-zh-rCN/strings.xml
@@ -66,11 +66,20 @@
<string name="picker_preview" msgid="6257414886055861039">"预览"</string>
<string name="picker_work_profile" msgid="2083221066869141576">"切换到工作资料"</string>
<string name="picker_personal_profile" msgid="639484258397758406">"切换到个人资料"</string>
+ <string name="picker_personal_profile_label" msgid="6189198163209597344">"个人"</string>
<string name="picker_profile_admin_title" msgid="4172022376418293777">"已被您的管理员禁止"</string>
<string name="picker_profile_admin_msg_from_personal" msgid="1941639895084555723">"不允许通过个人应用访问工作数据"</string>
<string name="picker_profile_admin_msg_from_work" msgid="8048524337462790110">"不允许通过工作应用访问个人数据"</string>
+ <!-- no translation found for picker_profile_admin_msg (4060112887923255178) -->
+ <skip />
+ <!-- no translation found for picker_profile_switch_message (1133817927412489487) -->
+ <skip />
<string name="picker_profile_work_paused_title" msgid="382212880704235925">"工作应用已暂停"</string>
+ <!-- no translation found for picker_profile_paused_title (2079739512895529028) -->
+ <skip />
<string name="picker_profile_work_paused_msg" msgid="6321552322125246726">"如需打开工作照片,请打开您的工作应用,然后重试"</string>
+ <!-- no translation found for picker_profile_paused_msg (1215076898583993782) -->
+ <skip />
<string name="picker_privacy_message" msgid="9132700451027116817">"此应用只能访问您选择的照片"</string>
<string name="picker_header_permissions" msgid="675872774407768495">"选择允许此应用访问的照片和视频"</string>
<string name="picker_album_item_count" msgid="4420723302534177596">"{count,plural, =1{<xliff:g id="COUNT_0">^1</xliff:g> 个}other{<xliff:g id="COUNT_1">^1</xliff:g> 个}}"</string>
@@ -160,4 +169,8 @@
<string name="dialog_error_message" msgid="5120432204743681606">"请稍后再试。问题解决后,您就能看到这些照片了。"</string>
<string name="dialog_error_title" msgid="636349284077820636">"部分照片无法加载"</string>
<string name="dialog_button_text" msgid="351366485240852280">"知道了"</string>
+ <!-- no translation found for permlab_accessMediaOwnerPackageName (3849443148060165651) -->
+ <skip />
+ <!-- no translation found for permdesc_accessMediaOwnerPackageName (7381563109363105371) -->
+ <skip />
</resources>
diff --git a/res/values-zh-rHK/strings.xml b/res/values-zh-rHK/strings.xml
index fa75494..fa46af6 100644
--- a/res/values-zh-rHK/strings.xml
+++ b/res/values-zh-rHK/strings.xml
@@ -66,11 +66,20 @@
<string name="picker_preview" msgid="6257414886055861039">"預覽"</string>
<string name="picker_work_profile" msgid="2083221066869141576">"切換至工作設定檔"</string>
<string name="picker_personal_profile" msgid="639484258397758406">"切換至個人設定檔"</string>
+ <string name="picker_personal_profile_label" msgid="6189198163209597344">"個人"</string>
<string name="picker_profile_admin_title" msgid="4172022376418293777">"管理員禁止此操作"</string>
<string name="picker_profile_admin_msg_from_personal" msgid="1941639895084555723">"個人應用程式不得存取工作資料"</string>
<string name="picker_profile_admin_msg_from_work" msgid="8048524337462790110">"工作應用程式不得存取個人資料"</string>
+ <!-- no translation found for picker_profile_admin_msg (4060112887923255178) -->
+ <skip />
+ <!-- no translation found for picker_profile_switch_message (1133817927412489487) -->
+ <skip />
<string name="picker_profile_work_paused_title" msgid="382212880704235925">"已暫停工作應用程式"</string>
+ <!-- no translation found for picker_profile_paused_title (2079739512895529028) -->
+ <skip />
<string name="picker_profile_work_paused_msg" msgid="6321552322125246726">"如要開啟工作相片,請開啟工作應用程式,然後再試一次"</string>
+ <!-- no translation found for picker_profile_paused_msg (1215076898583993782) -->
+ <skip />
<string name="picker_privacy_message" msgid="9132700451027116817">"此應用程式只能存取你選取的相片"</string>
<string name="picker_header_permissions" msgid="675872774407768495">"選擇允許此應用程式存取的相片和影片"</string>
<string name="picker_album_item_count" msgid="4420723302534177596">"{count,plural, =1{<xliff:g id="COUNT_0">^1</xliff:g> 個項目}other{<xliff:g id="COUNT_1">^1</xliff:g> 個項目}}"</string>
@@ -160,4 +169,8 @@
<string name="dialog_error_message" msgid="5120432204743681606">"請稍後再試。相片會在問題解決後顯示。"</string>
<string name="dialog_error_title" msgid="636349284077820636">"部分相片無法載入"</string>
<string name="dialog_button_text" msgid="351366485240852280">"知道了"</string>
+ <!-- no translation found for permlab_accessMediaOwnerPackageName (3849443148060165651) -->
+ <skip />
+ <!-- no translation found for permdesc_accessMediaOwnerPackageName (7381563109363105371) -->
+ <skip />
</resources>
diff --git a/res/values-zh-rTW/strings.xml b/res/values-zh-rTW/strings.xml
index 63c3401..f224f97 100644
--- a/res/values-zh-rTW/strings.xml
+++ b/res/values-zh-rTW/strings.xml
@@ -66,11 +66,20 @@
<string name="picker_preview" msgid="6257414886055861039">"預覽"</string>
<string name="picker_work_profile" msgid="2083221066869141576">"切換至工作資料夾"</string>
<string name="picker_personal_profile" msgid="639484258397758406">"切換至個人資料夾"</string>
+ <string name="picker_personal_profile_label" msgid="6189198163209597344">"個人"</string>
<string name="picker_profile_admin_title" msgid="4172022376418293777">"管理員已禁止這項操作"</string>
<string name="picker_profile_admin_msg_from_personal" msgid="1941639895084555723">"個人應用程式不得存取工作資料"</string>
<string name="picker_profile_admin_msg_from_work" msgid="8048524337462790110">"工作應用程式不得存取個人資料"</string>
+ <!-- no translation found for picker_profile_admin_msg (4060112887923255178) -->
+ <skip />
+ <!-- no translation found for picker_profile_switch_message (1133817927412489487) -->
+ <skip />
<string name="picker_profile_work_paused_title" msgid="382212880704235925">"工作應用程式目前為暫停狀態"</string>
+ <!-- no translation found for picker_profile_paused_title (2079739512895529028) -->
+ <skip />
<string name="picker_profile_work_paused_msg" msgid="6321552322125246726">"如要開啟工作資料夾的相片或影片,請開啟工作應用程式,然後再試一次"</string>
+ <!-- no translation found for picker_profile_paused_msg (1215076898583993782) -->
+ <skip />
<string name="picker_privacy_message" msgid="9132700451027116817">"這個應用程式只能存取你選取的相片"</string>
<string name="picker_header_permissions" msgid="675872774407768495">"選取允許這個應用程式存取的相片和影片"</string>
<string name="picker_album_item_count" msgid="4420723302534177596">"{count,plural, =1{<xliff:g id="COUNT_0">^1</xliff:g> 個項目}other{<xliff:g id="COUNT_1">^1</xliff:g> 個項目}}"</string>
@@ -160,4 +169,8 @@
<string name="dialog_error_message" msgid="5120432204743681606">"請稍後再試。問題解決後,你就可以存取相片。"</string>
<string name="dialog_error_title" msgid="636349284077820636">"無法載入部分相片"</string>
<string name="dialog_button_text" msgid="351366485240852280">"我知道了"</string>
+ <!-- no translation found for permlab_accessMediaOwnerPackageName (3849443148060165651) -->
+ <skip />
+ <!-- no translation found for permdesc_accessMediaOwnerPackageName (7381563109363105371) -->
+ <skip />
</resources>
diff --git a/res/values-zu/strings.xml b/res/values-zu/strings.xml
index c0a63ae..338dbcb 100644
--- a/res/values-zu/strings.xml
+++ b/res/values-zu/strings.xml
@@ -66,11 +66,20 @@
<string name="picker_preview" msgid="6257414886055861039">"Hlola kuqala"</string>
<string name="picker_work_profile" msgid="2083221066869141576">"Shintshela kokmsebenzi"</string>
<string name="picker_personal_profile" msgid="639484258397758406">"Shintshela kokomuntu siqu"</string>
+ <string name="picker_personal_profile_label" msgid="6189198163209597344">"Okomuntu siqu"</string>
<string name="picker_profile_admin_title" msgid="4172022376418293777">"Kuvinjwe ngumphathi wakho"</string>
<string name="picker_profile_admin_msg_from_personal" msgid="1941639895084555723">"Ukufinyelela idatha evela ku-app yomuntu siqu akuvunyelwe"</string>
<string name="picker_profile_admin_msg_from_work" msgid="8048524337462790110">"Ukufinyelela idatha yomuntu siqu evela ku-app yomsebenzi akuvunyelwe"</string>
+ <!-- no translation found for picker_profile_admin_msg (4060112887923255178) -->
+ <skip />
+ <!-- no translation found for picker_profile_switch_message (1133817927412489487) -->
+ <skip />
<string name="picker_profile_work_paused_title" msgid="382212880704235925">"Ama-app okusebenza amisiwe"</string>
+ <!-- no translation found for picker_profile_paused_title (2079739512895529028) -->
+ <skip />
<string name="picker_profile_work_paused_msg" msgid="6321552322125246726">"Ukuze uvule izithombe zomsebenzi, vula ama-app wakho womsebenzi bese uzama futhi"</string>
+ <!-- no translation found for picker_profile_paused_msg (1215076898583993782) -->
+ <skip />
<string name="picker_privacy_message" msgid="9132700451027116817">"Le app ingafinyelela izithombe ozikhethayo kuphela"</string>
<string name="picker_header_permissions" msgid="675872774407768495">"Khetha izithombe namavidiyo ovumela le app ukuthi iwafinyelele"</string>
<string name="picker_album_item_count" msgid="4420723302534177596">"{count,plural, =1{into <xliff:g id="COUNT_0">^1</xliff:g>}one{izinto <xliff:g id="COUNT_1">^1</xliff:g>}other{izinto <xliff:g id="COUNT_1">^1</xliff:g>}}"</string>
@@ -160,4 +169,8 @@
<string name="dialog_error_message" msgid="5120432204743681606">"Zama futhi emuva kwesikhathi. Izithombe zakho zizotholakala uma inkinga isixazululiwe."</string>
<string name="dialog_error_title" msgid="636349284077820636">"Ayikwazi ukulayisha ezinye Izithombe"</string>
<string name="dialog_button_text" msgid="351366485240852280">"Ngiyezwa"</string>
+ <!-- no translation found for permlab_accessMediaOwnerPackageName (3849443148060165651) -->
+ <skip />
+ <!-- no translation found for permdesc_accessMediaOwnerPackageName (7381563109363105371) -->
+ <skip />
</resources>
diff --git a/res/values/dimens.xml b/res/values/dimens.xml
index 7681262..0f45611 100644
--- a/res/values/dimens.xml
+++ b/res/values/dimens.xml
@@ -53,6 +53,8 @@
<dimen name="picker_photo_item_spacing">3dp</dimen>
+ <dimen name="popup_window_title_icon_padding">10dp</dimen>
+
<!-- Photo Picker recycler view bottom padding for progress bar -->
<dimen name="picker_recycler_view_bottom_padding">78dp</dimen>
diff --git a/res/values/media_provider_styles.xml b/res/values/media_provider_styles.xml
new file mode 100644
index 0000000..91e3cfc
--- /dev/null
+++ b/res/values/media_provider_styles.xml
@@ -0,0 +1,43 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2024 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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:androidprv="http://schemas.android.com/apk/prv/res/android">
+
+ <!-- Sets the theme of PermissionActivity -->
+ <style name="PickerDialogTheme"
+ parent="@android:style/Theme.DeviceDefault.Light.Dialog.Alert">
+ <item name="android:windowNoTitle">true</item>
+ </style>
+
+ <!-- Sets attributes for CacheClearingAlertDialogTheme -->
+ <style name="AlertDialogTheme"
+ parent="@android:style/Theme.DeviceDefault.Light.Dialog.Alert"/>
+
+ <!-- Sets the theme of CacheClearingActivity -->
+ <style name="CacheClearingAlertDialogTheme"
+ parent="@android:style/Theme.DeviceDefault.Light.Dialog.Alert">
+ <item name="android:windowIsTranslucent">true</item>
+ <item name="android:windowBackground">@android:color/transparent</item>
+ <item name="android:windowContentOverlay">@null</item>
+ <item name="android:windowNoTitle">true</item>
+ <item name="android:windowIsFloating">true</item>
+ <item name="android:backgroundDimEnabled">true</item>
+ <item name="android:alertDialogTheme">@style/AlertDialogTheme</item>
+ </style>
+
+</resources>
\ No newline at end of file
diff --git a/res/values/styles.xml b/res/values/photopicker_styles.xml
similarity index 78%
rename from res/values/styles.xml
rename to res/values/photopicker_styles.xml
index 55a1f9d..dfecc29 100644
--- a/res/values/styles.xml
+++ b/res/values/photopicker_styles.xml
@@ -1,51 +1,96 @@
<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2014 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
+<!--
+ ~ Copyright (C) 2024 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.
+ -->
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:androidprv="http://schemas.android.com/apk/prv/res/android">
- <style name="PickerDialogTheme"
- parent="@android:style/Theme.DeviceDefault.Light.Dialog.Alert">
- <item name="android:windowNoTitle">true</item>
- </style>
-
- <style name="AlertDialogTheme"
- parent="@android:style/Theme.DeviceDefault.Light.Dialog.Alert"/>
-
- <style name="CacheClearingAlertDialogTheme"
- parent="@android:style/Theme.DeviceDefault.Light.Dialog.Alert">
- <item name="android:windowIsTranslucent">true</item>
- <item name="android:windowBackground">@android:color/transparent</item>
- <item name="android:windowContentOverlay">@null</item>
- <item name="android:windowNoTitle">true</item>
- <item name="android:windowIsFloating">true</item>
+ <!-- This is the default theme for the picker extracting attributes from the device's
+ default theme -->
+ <style name="PickerDefaultTheme" parent="@android:style/Theme.DeviceDefault.DayNight">
+ <!-- System | Widget section -->
+ <item name="actionOverflowButtonStyle">@style/OverflowButtonStyle</item>
<item name="android:backgroundDimEnabled">true</item>
- <item name="android:alertDialogTheme">@style/AlertDialogTheme</item>
+ <item name="android:navigationBarColor">@color/picker_background_color</item>
+ <item name="android:statusBarColor">@android:color/transparent</item>
+ <item name="android:windowBackground">@android:color/transparent</item>
+ <item name="android:windowIsTranslucent">true</item>
+ <item name="android:windowNoTitle">true</item>
</style>
+ <!-- Sets button attribute for PickerDefaultTheme -->
+ <style name="OverflowButtonStyle" parent="Widget.AppCompat.ActionButton.Overflow">
+ <item name="android:minWidth">@dimen/button_touch_size</item>
+ </style>
+
+ <!-- Defines theme of the PhotoPickerSettingsActivity -->
+ <style name="PickerSettingsDefaultTheme" parent="PickerDefaultTheme">
+ <item name="android:windowIsTranslucent">false</item>
+ <item name="android:listPreferredItemHeightSmall">
+ @dimen/picker_settings_list_item_height
+ </item>
+ <item name="android:listPreferredItemPaddingEnd">
+ @dimen/picker_settings_list_item_padding_end
+ </item>
+ </style>
+
+ <!-- Defines the theme of the PhotoPickerActivity -->
+ <style name="PickerMaterialTheme" parent="@style/Theme.Material3.DayNight.NoActionBar">
+ <item name="materialAlertDialogTheme">@style/ProfileDialogTheme</item>
+ <item name="pickerDragBarColor">#DADCE0</item>
+ <item name="pickerHighlightColor">?android:attr/colorAccent</item>
+ <item name="pickerHighlightTextColor">@android:color/white</item>
+ <item name="pickerProfileButtonColor">#E8F0FE</item>
+ <item name="pickerDisabledProfileButtonColor">@android:color/white</item>
+ <item name="pickerProfileButtonTextColor">#0B57D0</item>
+ <item name="pickerDisabledProfileButtonTextColor">#42757575</item>
+ <item name="pickerSelectedTabBackgroundColor">#E8F0FE</item>
+ <item name="pickerSelectedTabTextColor">#185ABC</item>
+ <item name="pickerTabBackgroundColor">@color/picker_background_color</item>
+ <item name="pickerTextColor">?android:attr/textColorPrimary</item>
+ <item name="pickerSelectedColor">?android:attr/colorAccent</item>
+ <item name="pickerProfileDialogButtonAndIconColor">#1A73E8</item>
+ <item name="pickerProfileDialogTitleColor">#202124</item>
+ <item name="pickerProfileDialogBodyColor">#5F6368</item>
+ <item name="pickerProfileDialogBackgroundColor">@android:color/white</item>
+ <!-- TODO(b/195009152): Final banner colors to be updated once received -->
+ <item name="pickerBannerBackgroundColor">@color/picker_background_color</item>
+ <item name="pickerBannerStrokeColor">@android:color/white</item>
+ <item name="pickerBannerPrimaryTextColor">?android:attr/textColorSecondary</item>
+ <item name="pickerBannerSecondaryTextColor">?android:attr/textColorPrimary</item>
+ <item name="pickerBannerButtonTextColor">?android:attr/colorAccent</item>
+ <item name="categoryDefaultThumbnailColor">?attr/colorOnSurfaceVariant</item>
+ <item name="categoryDefaultThumbnailCircleColor">?attr/colorSurfaceVariant</item>
+ </style>
+
+ <!-- Sets the style of the 'view selected' button in the picker bottom bar and the
+ 'selected check button' in the PreviewFragment -->
<style name="MaterialBorderlessButtonStyle"
parent="@style/Widget.MaterialComponents.Button.TextButton">
<item name="android:textAppearance">@style/PickerButtonTextAppearance</item>
</style>
+ <!-- Sets the style of the 'add' button in both the picker bottom bar and in the
+ PreviewFragment -->
<style name="MaterialButtonStyle"
parent="@style/Widget.MaterialComponents.Button.UnelevatedButton">
<item name="android:textAppearance">@style/PickerButtonTextAppearance</item>
</style>
+ <!-- Sets material alert dialog attributes in PickerMaterialTheme -->
<style name="ProfileDialogTheme"
parent="@style/ThemeOverlay.MaterialComponents.MaterialAlertDialog.Centered">
<item name="shapeAppearanceOverlay">@style/ShapeAppearance</item>
@@ -57,6 +102,10 @@
<item name="buttonBarPositiveButtonStyle">@style/PositiveButtonStyle</item>
</style>
+
+ <!-- The following five styles are used to set the attributes of the shape, button and material
+ alert dialog in ProfileDialogTheme -->
+
<style name="ShapeAppearance">
<item name="cornerFamily">rounded</item>
<item name="cornerSize">@dimen/picker_profile_dialog_radius</item>
@@ -88,51 +137,7 @@
<item name="android:layout_height">@dimen/picker_profile_dialog_icon_height</item>
</style>
- <style name="PickerDefaultTheme" parent="@android:style/Theme.DeviceDefault.DayNight">
- <!-- System | Widget section -->
- <item name="actionOverflowButtonStyle">@style/OverflowButtonStyle</item>
- <item name="android:backgroundDimEnabled">true</item>
- <item name="android:navigationBarColor">@color/picker_background_color</item>
- <item name="android:statusBarColor">@android:color/transparent</item>
- <item name="android:windowBackground">@android:color/transparent</item>
- <item name="android:windowIsTranslucent">true</item>
- <item name="android:windowNoTitle">true</item>
- </style>
-
- <style name="PickerSettingsDefaultTheme" parent="PickerDefaultTheme">
- <item name="android:windowIsTranslucent">false</item>
- <item name="android:listPreferredItemHeightSmall">@dimen/picker_settings_list_item_height</item>
- <item name="android:listPreferredItemPaddingEnd">@dimen/picker_settings_list_item_padding_end</item>
- </style>
-
- <style name="PickerMaterialTheme" parent="@style/Theme.Material3.DayNight.NoActionBar">
- <item name="materialAlertDialogTheme">@style/ProfileDialogTheme</item>
- <item name="pickerDragBarColor">#DADCE0</item>
- <item name="pickerHighlightColor">?android:attr/colorAccent</item>
- <item name="pickerHighlightTextColor">@android:color/white</item>
- <item name="pickerProfileButtonColor">#E8F0FE</item>
- <item name="pickerDisabledProfileButtonColor">@android:color/white</item>
- <item name="pickerProfileButtonTextColor">#0B57D0</item>
- <item name="pickerDisabledProfileButtonTextColor">#42757575</item>
- <item name="pickerSelectedTabBackgroundColor">#E8F0FE</item>
- <item name="pickerSelectedTabTextColor">#185ABC</item>
- <item name="pickerTabBackgroundColor">@color/picker_background_color</item>
- <item name="pickerTextColor">?android:attr/textColorPrimary</item>
- <item name="pickerSelectedColor">?android:attr/colorAccent</item>
- <item name="pickerProfileDialogButtonAndIconColor">#1A73E8</item>
- <item name="pickerProfileDialogTitleColor">#202124</item>
- <item name="pickerProfileDialogBodyColor">#5F6368</item>
- <item name="pickerProfileDialogBackgroundColor">@android:color/white</item>
- <!-- TODO(b/195009152): Final banner colors to be updated once received -->
- <item name="pickerBannerBackgroundColor">@color/picker_background_color</item>
- <item name="pickerBannerStrokeColor">@android:color/white</item>
- <item name="pickerBannerPrimaryTextColor">?android:attr/textColorSecondary</item>
- <item name="pickerBannerSecondaryTextColor">?android:attr/textColorPrimary</item>
- <item name="pickerBannerButtonTextColor">?android:attr/colorAccent</item>
- <item name="categoryDefaultThumbnailColor">?attr/colorOnSurfaceVariant</item>
- <item name="categoryDefaultThumbnailCircleColor">?attr/colorSurfaceVariant</item>
- </style>
-
+ <!-- Sets the style for the dismiss and action buttons in picker banner -->
<style name="PickerBannerButtonTheme"
parent = "@style/Widget.Material3.Button.TextButton">
<item name="android:layout_width">wrap_content</item>
@@ -142,23 +147,23 @@
<item name="android:textColor">?attr/pickerBannerButtonTextColor</item>
</style>
- <style name="OverflowButtonStyle" parent="Widget.AppCompat.ActionButton.Overflow">
- <item name="android:minWidth">@dimen/button_touch_size</item>
- </style>
-
+ <!-- Sets the style of the progress dialog shown after selecting the media using the picker -->
<style name="SelectedMediaPreloaderDialogTheme"
parent="@style/ThemeOverlay.MaterialComponents.MaterialAlertDialog.Centered">
<item name="android:textColor">?attr/colorOnSurfaceVariant</item>
<item name="materialAlertDialogTitleTextStyle">@style/AlertDialogTitleStyle</item>
</style>
+ <!-- Sets text attributes for SelectedMediaPreloaderDialogTheme -->
+ <style name="AlertDialogTitleStyle"
+ parent="@style/MaterialAlertDialog.MaterialComponents.Title.Text.CenterStacked">
+ <item name="android:textColor">?attr/colorOnSurface</item>
+ </style>
+
+ <!-- Sets the style of the cancel button of the selected media progress dialog -->
<style name="ProgressDialogCancelButtonStyle"
parent="@style/Widget.MaterialComponents.Button.TextButton">
<item name="android:textColor">?attr/colorOnSurface</item>
</style>
- <style name="AlertDialogTitleStyle"
- parent="@style/MaterialAlertDialog.MaterialComponents.Title.Text.CenterStacked">
- <item name="android:textColor">?attr/colorOnSurface</item>
- </style>
</resources>
diff --git a/res/values/strings.xml b/res/values/strings.xml
index 748e7c5..46e07b1 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -162,6 +162,8 @@
<string name="picker_work_profile">Switch to work</string>
<!-- The text of the switching work/personal profile in PhotoPicker. [CHAR LIMIT=80] -->
<string name="picker_personal_profile">Switch to personal</string>
+ <!-- personal profile label in PhotoPicker -->
+ <string name="picker_personal_profile_label">Personal</string>
<!-- The title for error dialog in PhotoPicker when the admin blocks cross user interaction for
the intent. [CHAR LIMIT=100] -->
<string name="picker_profile_admin_title">Blocked by your admin</string>
@@ -171,13 +173,23 @@
<!-- The message for error dialog in PhotoPicker when the admin blocks cross user interaction
for the intent. [CHAR LIMIT=NONE] -->
<string name="picker_profile_admin_msg_from_work">Accessing personal data from a work app is not permitted</string>
+ <!-- The message for error dialog in PhotoPicker when the admin of any user profile blocks cross
+ user interaction for the intent. [CHAR LIMIT=NONE] -->
+ <string name="picker_profile_admin_msg">Accessing <xliff:g id="profile1" example="work">%1$s</xliff:g> data from a <xliff:g id="profile2" example="personal">%2$s</xliff:g> app is not permitted</string>
+ <!-- The text of the switching profile in PhotoPicker. [CHAR LIMIT=80] -->
+ <string name="picker_profile_switch_message">Switch to <xliff:g id="profile" example="personal">%s</xliff:g></string>
<!-- The title of the error dialog in PhotoPicker when the user tries to switch to work content,
but work profile is off. [CHAR LIMIT=80] -->
<string name="picker_profile_work_paused_title">Work apps are paused</string>
+ <!-- The title of the error dialog in PhotoPicker when the user tries to switch to other profile
+ content, but the other profile is off. [CHAR LIMIT=80] -->
+ <string name="picker_profile_paused_title"><xliff:g id="profile" example="work">%s</xliff:g> apps are paused</string>
<!-- The message of the error dialog in PhotoPicker when the user tries to switch to work
content, but work profile is off. [CHAR LIMIT=NONE] -->
<string name="picker_profile_work_paused_msg">To open work photos, turn on your work apps then try again</string>
-
+ <!-- The message of the error dialog in PhotoPicker when the user tries to switch to other
+ profile content, but the other profile is off. [CHAR LIMIT=NONE] -->
+ <string name="picker_profile_paused_msg">To open <xliff:g id="profile1" example="work">%1$s</xliff:g> photos, turn on your <xliff:g id="profile2" example="work">%2$s</xliff:g> apps then try again</string>
<!-- PhotoPicker privacy message. [CHAR LIMIT=80] -->
<string name="picker_privacy_message">This app can only access the photos you select</string>
<!-- PhotoPicker header message in permission select mode -->
@@ -523,4 +535,9 @@
<!-- Error dialog OK button text-->
<string name="dialog_button_text">Got it</string>
+
+ <!-- Title of ACCESS_OWNER_PACKAGE_NAME permission-->
+ <string name="permlab_accessMediaOwnerPackageName">Access media owner package name</string>
+ <!-- Description of ACCESS_OWNER_PACKAGE_NAME permission-->
+ <string name="permdesc_accessMediaOwnerPackageName">Allows the app to access owner package names of all accessible media files.</string>
</resources>
diff --git a/src/com/android/providers/media/ConfigStore.java b/src/com/android/providers/media/ConfigStore.java
index 828d620..1173e72 100644
--- a/src/com/android/providers/media/ConfigStore.java
+++ b/src/com/android/providers/media/ConfigStore.java
@@ -69,6 +69,7 @@
boolean DEFAULT_CLOUD_MEDIA_IN_PHOTO_PICKER_ENABLED = true;
boolean DEFAULT_ENFORCE_CLOUD_PROVIDER_ALLOWLIST = true;
boolean DEFAULT_PICKER_CHOICE_MANAGED_SELECTION_ENABLED = true;
+ boolean DEFAULT_PICKER_PRIVATE_SPACE_ENABLED = false;
/**
* @return if the Cloud-Media-in-Photo-Picker enabled (e.g. platform will recognize and
@@ -79,6 +80,13 @@
}
/**
+ * @return if the Private-Space-in-Photo-Picker enabled
+ */
+ default boolean isPrivateSpaceInPhotoPickerEnabled() {
+ return DEFAULT_PICKER_PRIVATE_SPACE_ENABLED;
+ }
+
+ /**
* @return if the Picker-Choice_Managed_selection is enabled.
*/
default boolean isPickerChoiceManagedSelectionEnabled() {
@@ -283,6 +291,10 @@
"picker_pick_images_respect_preload_selected_arg";
private static final String KEY_CLOUD_MEDIA_FEATURE_ENABLED = "cloud_media_feature_enabled";
+
+ @VisibleForTesting
+ public static final String KEY_PRIVATE_SPACE_FEATURE_ENABLED =
+ "private_space_feature_enabled";
private static final String KEY_PICKER_CHOICE_MANAGED_SELECTION_ENABLED =
"picker_choice_managed_selection_enabled";
private static final String KEY_CLOUD_MEDIA_PROVIDER_ALLOWLIST = "allowed_cloud_providers";
@@ -316,6 +328,14 @@
}
@Override
+ public boolean isPrivateSpaceInPhotoPickerEnabled() {
+ return getBooleanDeviceConfig(
+ NAMESPACE_MEDIAPROVIDER,
+ KEY_PRIVATE_SPACE_FEATURE_ENABLED,
+ DEFAULT_PICKER_PRIVATE_SPACE_ENABLED);
+ }
+
+ @Override
public boolean isPickerChoiceManagedSelectionEnabled() {
return getBooleanDeviceConfig(
NAMESPACE_MEDIAPROVIDER,
diff --git a/src/com/android/providers/media/DatabaseBackupAndRecovery.java b/src/com/android/providers/media/DatabaseBackupAndRecovery.java
index 6664697..c7c6885 100644
--- a/src/com/android/providers/media/DatabaseBackupAndRecovery.java
+++ b/src/com/android/providers/media/DatabaseBackupAndRecovery.java
@@ -155,6 +155,21 @@
private static Map<String, String> sOwnerIdRelationMap;
+ public static final String STABLE_URI_INTERNAL_PROPERTY =
+ "persist.sys.fuse.backup.internal_db_backup";
+
+ private static boolean STABLE_URI_INTERNAL_PROPERTY_VALUE = true;
+
+ public static final String STABLE_URI_EXTERNAL_PROPERTY =
+ "persist.sys.fuse.backup.external_volume_backup";
+
+ private static boolean STABLE_URI_EXTERNAL_PROPERTY_VALUE = false;
+
+ public static final String STABLE_URI_PUBLIC_PROPERTY =
+ "persist.sys.fuse.backup.public_db_backup";
+
+ private static boolean STABLE_URI_PUBLIC_PROPERTY_VALUE = false;
+
protected DatabaseBackupAndRecovery(ConfigStore configStore, VolumeCache volumeCache) {
mConfigStore = configStore;
mVolumeCache = volumeCache;
@@ -167,19 +182,18 @@
switch (volumeName) {
case MediaStore.VOLUME_INTERNAL:
return mConfigStore.isStableUrisForInternalVolumeEnabled()
- || SystemProperties.getBoolean("persist.sys.fuse.backup.internal_db_backup",
- /* defaultValue */ true);
+ || SystemProperties.getBoolean(STABLE_URI_INTERNAL_PROPERTY,
+ /* defaultValue */ STABLE_URI_INTERNAL_PROPERTY_VALUE);
case MediaStore.VOLUME_EXTERNAL_PRIMARY:
return mConfigStore.isStableUrisForExternalVolumeEnabled()
- || SystemProperties.getBoolean(
- "persist.sys.fuse.backup.external_volume_backup",
- /* defaultValue */ false);
+ || SystemProperties.getBoolean(STABLE_URI_EXTERNAL_PROPERTY,
+ /* defaultValue */ STABLE_URI_EXTERNAL_PROPERTY_VALUE);
default:
// public volume
return isStableUrisEnabled(MediaStore.VOLUME_EXTERNAL_PRIMARY)
&& mConfigStore.isStableUrisForPublicVolumeEnabled()
- || SystemProperties.getBoolean("persist.sys.fuse.backup.public_db_backup",
- /* defaultValue */ false);
+ || SystemProperties.getBoolean(STABLE_URI_PUBLIC_PROPERTY,
+ /* defaultValue */ STABLE_URI_PUBLIC_PROPERTY_VALUE);
}
}
@@ -208,6 +222,9 @@
}
final long startTime = SystemClock.elapsedRealtime();
+ int vol = MediaStore.VOLUME_EXTERNAL_PRIMARY.equalsIgnoreCase(volumeName)
+ ? MEDIA_PROVIDER_VOLUME_RECOVERY_REPORTED__VOLUME__EXTERNAL_PRIMARY
+ : MEDIA_PROVIDER_VOLUME_RECOVERY_REPORTED__VOLUME__PUBLIC;
try {
if (!new File(RECOVERY_DIRECTORY_PATH).exists()) {
new File(RECOVERY_DIRECTORY_PATH).mkdirs();
@@ -219,16 +236,31 @@
isStableUrisEnabled(MediaStore.VOLUME_INTERNAL) || isStableUrisEnabled(
MediaStore.VOLUME_EXTERNAL_PRIMARY))) {
// Setup internal and external volumes
+ MediaProviderStatsLog.write(
+ MediaProviderStatsLog.BACKUP_SETUP_STATUS_REPORTED,
+ MediaProviderStatsLog.BACKUP_SETUP_STATUS_REPORTED__STATUS__ATTEMPTED, vol);
fuseDaemon.setupVolumeDbBackup();
mSetupCompletePublicVolumes.add(volumeName);
+ MediaProviderStatsLog.write(
+ MediaProviderStatsLog.BACKUP_SETUP_STATUS_REPORTED,
+ MediaProviderStatsLog.BACKUP_SETUP_STATUS_REPORTED__STATUS__SUCCESS, vol);
} else if (isStableUrisEnabled(volumeName)) {
// Setup public volume
+ MediaProviderStatsLog.write(
+ MediaProviderStatsLog.BACKUP_SETUP_STATUS_REPORTED,
+ MediaProviderStatsLog.BACKUP_SETUP_STATUS_REPORTED__STATUS__ATTEMPTED, vol);
fuseDaemon.setupPublicVolumeDbBackup(volumeName);
mSetupCompletePublicVolumes.add(volumeName);
+ MediaProviderStatsLog.write(
+ MediaProviderStatsLog.BACKUP_SETUP_STATUS_REPORTED,
+ MediaProviderStatsLog.BACKUP_SETUP_STATUS_REPORTED__STATUS__SUCCESS, vol);
} else {
return;
}
} catch (IOException e) {
+ MediaProviderStatsLog.write(
+ MediaProviderStatsLog.BACKUP_SETUP_STATUS_REPORTED,
+ MediaProviderStatsLog.BACKUP_SETUP_STATUS_REPORTED__STATUS__FAILURE, vol);
Log.e(TAG, "Failure in setting up backup and recovery for volume: " + volumeName, e);
return;
} finally {
@@ -1047,4 +1079,30 @@
throw new RuntimeException("Timed out while waiting for ExternalStorageState "
+ "to be MEDIA_MOUNTED");
}
+
+ public void onDetachVolume(MediaVolume volume) {
+ String volumeName = volume.getName();
+ if (MediaStore.VOLUME_INTERNAL.equalsIgnoreCase(volumeName)) {
+ // Connection is removed as part of external_primary volume detach
+ return;
+ } else if (MediaStore.VOLUME_EXTERNAL_PRIMARY.equalsIgnoreCase(volumeName)) {
+ if (!isStableUrisEnabled(MediaStore.VOLUME_INTERNAL) && !isStableUrisEnabled(
+ MediaStore.VOLUME_EXTERNAL_PRIMARY)) {
+ return;
+ }
+ } else if (!isStableUrisEnabled(volumeName)) {
+ return;
+ }
+
+ FuseDaemon fuseDaemon;
+ try {
+ fuseDaemon = getFuseDaemonForPath(EXTERNAL_PRIMARY_ROOT_PATH);
+ fuseDaemon.removeLevelDbConnections(volumeName);
+ mSetupCompletePublicVolumes.remove(volumeName);
+ Log.i(TAG, "Successfully removed leveldb connections for volume:" + volumeName);
+ } catch (Exception e) {
+ Log.e(TAG, "Failure in removeLevelDbConnections execution.", e);
+ return;
+ }
+ }
}
diff --git a/src/com/android/providers/media/DatabaseHelper.java b/src/com/android/providers/media/DatabaseHelper.java
index 0fb7ff3..ee35595 100644
--- a/src/com/android/providers/media/DatabaseHelper.java
+++ b/src/com/android/providers/media/DatabaseHelper.java
@@ -603,6 +603,13 @@
return;
}
+
+ MediaProviderStatsLog.write(
+ MediaProviderStatsLog.MEDIA_PROVIDER_DATABASE_ROLLBACK_REPORTED, isInternal()
+ ?
+ MediaProviderStatsLog.MEDIA_PROVIDER_DATABASE_ROLLBACK_REPORTED__DATABASE_NAME__INTERNAL
+ :
+ MediaProviderStatsLog.MEDIA_PROVIDER_DATABASE_ROLLBACK_REPORTED__DATABASE_NAME__EXTERNAL);
Log.w(TAG, String.format(Locale.ROOT, "%s database inconsistency identified.", mName));
// Delete old data and create new schema.
recreateLatestSchema(db);
diff --git a/src/com/android/providers/media/LocalCallingIdentity.java b/src/com/android/providers/media/LocalCallingIdentity.java
index 6163b90..4bdde9f 100644
--- a/src/com/android/providers/media/LocalCallingIdentity.java
+++ b/src/com/android/providers/media/LocalCallingIdentity.java
@@ -21,10 +21,12 @@
import static com.android.providers.media.util.PermissionUtils.checkAppOpRequestInstallPackagesForSharedUid;
import static com.android.providers.media.util.PermissionUtils.checkIsLegacyStorageGranted;
import static com.android.providers.media.util.PermissionUtils.checkPermissionAccessMediaLocation;
+import static com.android.providers.media.util.PermissionUtils.checkPermissionAccessMediaOwnerPackageName;
import static com.android.providers.media.util.PermissionUtils.checkPermissionAccessMtp;
import static com.android.providers.media.util.PermissionUtils.checkPermissionDelegator;
import static com.android.providers.media.util.PermissionUtils.checkPermissionInstallPackages;
import static com.android.providers.media.util.PermissionUtils.checkPermissionManager;
+import static com.android.providers.media.util.PermissionUtils.checkPermissionQueryAllPackages;
import static com.android.providers.media.util.PermissionUtils.checkPermissionReadAudio;
import static com.android.providers.media.util.PermissionUtils.checkPermissionReadImages;
import static com.android.providers.media.util.PermissionUtils.checkPermissionReadStorage;
@@ -54,6 +56,7 @@
import android.os.SystemProperties;
import android.os.UserHandle;
import android.os.UserManager;
+import android.provider.MediaStore.Files.FileColumns;
import android.util.ArrayMap;
import android.util.Log;
@@ -81,7 +84,7 @@
private final Object lock = new Object();
@GuardedBy("lock")
- private int mDeletedFileCountBypassingDatabase = 0;
+ private int[] mDeletedFileCountsBypassingDatabase = new int[FileColumns.MEDIA_TYPE_COUNT];
private LocalCallingIdentity(Context context, int pid, int uid, UserHandle user,
String packageNameUnchecked, @Nullable String attributionTag) {
@@ -346,6 +349,9 @@
public static final int PERMISSION_READ_MEDIA_VISUAL_USER_SELECTED = 1 << 27;
+ public static final int PERMISSION_QUERY_ALL_PACKAGES = 1 << 28;
+ public static final int PERMISSION_ACCESS_MEDIA_OWNER_PACKAGE_NAME = 1 << 29;
+
private volatile int hasPermission;
private volatile int hasPermissionResolved;
@@ -424,6 +430,12 @@
case PERMISSION_READ_MEDIA_VISUAL_USER_SELECTED:
return checkPermissionReadVisualUserSelected(context, pid, uid, getPackageName(),
attributionTag, targetSdkIsAtLeastT);
+ case PERMISSION_QUERY_ALL_PACKAGES:
+ return checkPermissionQueryAllPackages(
+ context, pid, uid, getPackageName(), attributionTag);
+ case PERMISSION_ACCESS_MEDIA_OWNER_PACKAGE_NAME:
+ return checkPermissionAccessMediaOwnerPackageName(
+ context, pid, uid, getPackageName(), attributionTag);
default:
return false;
}
@@ -444,7 +456,9 @@
return true;
}
- return checkIsLegacyStorageGranted(context, uid, getPackageName(), attributionTag);
+ boolean targetSdkIsAtLeastR = getTargetSdkVersion() >= Build.VERSION_CODES.R;
+ return checkIsLegacyStorageGranted(context, uid, getPackageName(), attributionTag,
+ targetSdkIsAtLeastR);
}
private volatile boolean shouldBypass;
@@ -605,15 +619,42 @@
}
}
- protected void incrementDeletedFileCountBypassingDatabase() {
+ protected void incrementDeletedFileCountBypassingDatabase(int mediaType) {
synchronized (lock) {
- mDeletedFileCountBypassingDatabase++;
+ mDeletedFileCountsBypassingDatabase[mediaType]++;
}
}
- protected int getDeletedFileCountBypassingDatabase() {
+ private void clearDeletedFileCountsBypassingDatabase() {
synchronized (lock) {
- return mDeletedFileCountBypassingDatabase;
+ for (int i = 0; i < FileColumns.MEDIA_TYPE_COUNT; i++) {
+ mDeletedFileCountsBypassingDatabase[i] = 0;
+ }
+ }
+ }
+
+ protected int getDeletedFileTotalCountBypassingDatabase() {
+ synchronized (lock) {
+ int totalCount = 0;
+ for (int i = 0; i < FileColumns.MEDIA_TYPE_COUNT; i++) {
+ totalCount += mDeletedFileCountsBypassingDatabase[i];
+ }
+ return totalCount;
+ }
+ }
+
+ protected boolean hasDeletedFileCount() {
+ synchronized (lock) {
+ for (int i = 0; i < FileColumns.MEDIA_TYPE_COUNT; i++) {
+ if (mDeletedFileCountsBypassingDatabase[i] > 0) return true;
+ }
+ return false;
+ }
+ }
+
+ protected int[] getDeletedFileCountsBypassingDatabase() {
+ synchronized (lock) {
+ return mDeletedFileCountsBypassingDatabase;
}
}
@@ -671,6 +712,15 @@
}
/**
+ * Returns {@code true} if this package has permissions
+ * to access owner_package_name of any accessible file.
+ */
+ public boolean checkCallingPermissionsOwnerPackageName() {
+ return hasPermission(PERMISSION_QUERY_ALL_PACKAGES)
+ || hasPermission(PERMISSION_ACCESS_MEDIA_OWNER_PACKAGE_NAME);
+ }
+
+ /**
* Returns {@code true} if this package is a legacy app and has read permission
*/
public boolean isCallingPackageLegacyRead() {
@@ -695,26 +745,39 @@
}
protected void dump(PrintWriter writer) {
- if (getDeletedFileCountBypassingDatabase() <= 0) {
+ if (!hasDeletedFileCount()) {
return;
}
- writer.println(getDeletedFileCountLogMessage(uid, getPackageName(),
- getDeletedFileCountBypassingDatabase()));
+ writer.println(getDeletedFileCountsLogMessage(uid, getPackageName(),
+ getDeletedFileCountsBypassingDatabase()));
}
protected void dump(String reason) {
Log.i(TAG, "Invalidating LocalCallingIdentity cache for package " + packageName
+ ". Reason: " + reason);
- if (this.getDeletedFileCountBypassingDatabase() > 0) {
- Logging.logPersistent(getDeletedFileCountLogMessage(uid, getPackageName(),
- getDeletedFileCountBypassingDatabase()));
+ if (hasDeletedFileCount()) {
+ Logging.logPersistent(getDeletedFileCountsLogMessage(uid, getPackageName(),
+ getDeletedFileCountsBypassingDatabase()));
}
}
- private static String getDeletedFileCountLogMessage(int uid, String packageName,
- int deletedFilesCountBypassingDatabase) {
- return "uid=" + uid + " packageName=" + packageName + " deletedFilesCountBypassingDatabase="
- + deletedFilesCountBypassingDatabase;
+ protected void dump() {
+ if (hasDeletedFileCount()) {
+ Logging.logPersistent(getDeletedFileCountsLogMessage(uid, getPackageName(),
+ getDeletedFileCountsBypassingDatabase()));
+ clearDeletedFileCountsBypassingDatabase();
+ }
+ }
+
+ private static String getDeletedFileCountsLogMessage(int uid, String packageName,
+ int[] deletedFileCountsBypassingDatabase) {
+ final StringBuilder builder = new StringBuilder("uid=" + uid
+ + " packageName=" + packageName
+ + " deletedFilesCountsBypassingDatabase=");
+ for (int count: deletedFileCountsBypassingDatabase) {
+ builder.append(count).append(' ');
+ }
+ return builder.toString();
}
}
diff --git a/src/com/android/providers/media/MediaProvider.java b/src/com/android/providers/media/MediaProvider.java
index ec2d8c2..1b7dbb4 100644
--- a/src/com/android/providers/media/MediaProvider.java
+++ b/src/com/android/providers/media/MediaProvider.java
@@ -17,7 +17,6 @@
package com.android.providers.media;
import static android.Manifest.permission.ACCESS_MEDIA_LOCATION;
-import static android.Manifest.permission.QUERY_ALL_PACKAGES;
import static android.app.AppOpsManager.permissionToOp;
import static android.app.PendingIntent.FLAG_CANCEL_CURRENT;
import static android.app.PendingIntent.FLAG_IMMUTABLE;
@@ -27,7 +26,6 @@
import static android.content.ContentResolver.QUERY_ARG_SQL_SELECTION;
import static android.content.ContentResolver.QUERY_ARG_SQL_SELECTION_ARGS;
import static android.content.ContentResolver.QUERY_ARG_SQL_SORT_ORDER;
-import static android.content.pm.PackageManager.PERMISSION_DENIED;
import static android.content.pm.PackageManager.PERMISSION_GRANTED;
import static android.database.Cursor.FIELD_TYPE_BLOB;
import static android.provider.CloudMediaProviderContract.EXTRA_ASYNC_CONTENT_PROVIDER;
@@ -158,6 +156,7 @@
import static com.android.providers.media.util.Logging.TAG;
import static com.android.providers.media.util.PermissionUtils.checkPermissionSelf;
import static com.android.providers.media.util.PermissionUtils.checkPermissionShell;
+import static com.android.providers.media.util.PermissionUtils.checkPermissionSystem;
import static com.android.providers.media.util.StringUtils.componentStateToString;
import static com.android.providers.media.util.SyntheticPathUtils.REDACTED_URI_ID_PREFIX;
import static com.android.providers.media.util.SyntheticPathUtils.REDACTED_URI_ID_SIZE;
@@ -271,6 +270,7 @@
import android.util.SparseArray;
import android.webkit.MimeTypeMap;
+import androidx.annotation.ChecksSdkIntAtLeast;
import androidx.annotation.GuardedBy;
import androidx.annotation.Keep;
import androidx.annotation.NonNull;
@@ -293,6 +293,7 @@
import com.android.providers.media.photopicker.data.PickerDbFacade;
import com.android.providers.media.photopicker.data.PickerSyncRequestExtras;
import com.android.providers.media.photopicker.sync.PickerSyncLockManager;
+import com.android.providers.media.photopicker.util.exceptions.UnableToAcquireLockException;
import com.android.providers.media.playlist.Playlist;
import com.android.providers.media.scan.MediaScanner;
import com.android.providers.media.scan.MediaScanner.ScanReason;
@@ -316,7 +317,6 @@
import com.android.providers.media.util.XAttrUtils;
import com.android.providers.media.util.XmpInterface;
-import com.google.common.base.Strings;
import com.google.common.hash.Hashing;
import org.jetbrains.annotations.NotNull;
@@ -508,6 +508,12 @@
*/
private static final int MAX_SECTION_NAME_LEN = 127;
+ /**
+ * This string is a copy of
+ * {@link com.android.settingslib.drawer.TileUtils.META_DATA_PREFERENCE_SUMMARY}
+ */
+ private static final String META_DATA_PREFERENCE_SUMMARY = "com.android.settings.summary";
+
@GuardedBy("mPendingOpenInfo")
private final Map<Integer, PendingOpenInfo> mPendingOpenInfo = new ArrayMap<>();
@@ -1565,6 +1571,14 @@
public void onIdleMaintenance(@NonNull CancellationSignal signal) {
final long startTime = SystemClock.elapsedRealtime();
+
+ // Print # of deleted files
+ synchronized (mCachedCallingIdentityForFuse) {
+ for (int i = 0; i < mCachedCallingIdentityForFuse.size(); i++) {
+ mCachedCallingIdentityForFuse.valueAt(i).dump("Idle maintenance");
+ }
+ }
+
// Trim any stale log files before we emit new events below
Logging.trimPersistent();
@@ -3792,21 +3806,27 @@
onLocaleChanged(false);
}
- Cursor c = qb.query(helper, projection, queryArgs, signal);
+ Cursor c;
- // Starting U, we are filtering owner package names for apps without visibility
- // on other packages. Apps with QUERY_ALL_PACKAGES permission are not affected.
- if (shouldFilterOwnerPackageNameFlag() && isApplicableForOwnerPackageNameFiltering(c)) {
- final long startTime = SystemClock.elapsedRealtime();
- final String[] resultOwnerPackageNames = getOwnerPackageNames(c);
- if (resultOwnerPackageNames.length != 0) {
- final Set<String> queryablePackages = getQueryablePackages(resultOwnerPackageNames);
- if (resultOwnerPackageNames.length != queryablePackages.size()) {
- c = filterOwnerPackageNames(c, queryablePackages);
- }
- }
- final long durationMillis = SystemClock.elapsedRealtime() - startTime;
- Log.d(TAG, "Filtering owner package names took " + durationMillis + " ms");
+ if (shouldFilterOwnerPackageNameFlag()
+ && shouldFilterOwnerPackageNameInProjection(qb, projection)) {
+ Log.i(TAG, String.format("Filtering owner package name for %s, projection: %s",
+ mCallingIdentity.get().getPackageName(), Arrays.toString(projection)));
+
+ // Get a list of all owner_package_names in the result
+ final String[] ownerPackageNamesArr = getAllOwnerPackageNames(qb, helper,
+ queryArgs, signal);
+
+ // Get a list of queryable owner_package_names out of all
+ final Set<String> queryablePackages = getQueryablePackages(ownerPackageNamesArr);
+
+ // Substitute owner_package_name column with following:
+ // CASE WHEN owner_package_name IN ('queryablePackageA','queryablePackageB')
+ // THEN owner_package_name ELSE NULL END AS owner_package_name
+ final String[] newProjection = prepareSubstitution(qb, projection, queryablePackages);
+ c = qb.query(helper, newProjection, queryArgs, signal);
+ } else {
+ c = qb.query(helper, projection, queryArgs, signal);
}
if (c != null && !forSelf) {
@@ -3835,6 +3855,100 @@
return c;
}
+ /**
+ * Constructs the following projection string:
+ * CASE WHEN owner_package_name IN ("queryablePackageA","queryablePackageB")
+ * THEN owner_package_name ELSE NULL END AS owner_package_name
+ */
+ private String constructOwnerPackageNameProjection(Set<String> queryablePackages) {
+ final String packageNames = String.join(",", queryablePackages
+ .stream()
+ .map(name -> ("'" + name + "'"))
+ .collect(Collectors.toList()));
+
+ final StringBuilder newProjection = new StringBuilder()
+ .append("CASE WHEN ")
+ .append(OWNER_PACKAGE_NAME)
+ .append(" IN (")
+ .append(packageNames)
+ .append(") THEN ")
+ .append(OWNER_PACKAGE_NAME)
+ .append(" ELSE NULL END AS ")
+ .append(OWNER_PACKAGE_NAME);
+
+ Log.d(TAG, "Constructed owner_package_name substitution: " + newProjection);
+ return newProjection.toString();
+ }
+
+ private String[] getAllOwnerPackageNames(SQLiteQueryBuilder qb, DatabaseHelper helper,
+ Bundle queryArgs, CancellationSignal signal) {
+ final SQLiteQueryBuilder qbCopy = new SQLiteQueryBuilder(qb);
+ qbCopy.setDistinct(true);
+ qbCopy.appendWhereStandalone(OWNER_PACKAGE_NAME + " <> '' AND "
+ + OWNER_PACKAGE_NAME + " <> 'null' AND " + OWNER_PACKAGE_NAME + " IS NOT NULL");
+ final Cursor ownerPackageNames = qbCopy.query(helper, new String[]{OWNER_PACKAGE_NAME},
+ queryArgs, signal);
+
+ final String[] ownerPackageNamesArr = new String[ownerPackageNames.getCount()];
+ int i = 0;
+ while (ownerPackageNames.moveToNext()) {
+ ownerPackageNamesArr[i++] = ownerPackageNames.getString(0);
+ }
+ return ownerPackageNamesArr;
+ }
+
+ private String[] prepareSubstitution(SQLiteQueryBuilder qb,
+ String[] projection, Set<String> queryablePackages) {
+ projection = maybeReplaceNullProjection(projection, qb);
+ if (qb.getProjectionAllowlist() == null) {
+ qb.setProjectionAllowlist(new ArrayList<>());
+ }
+ final String[] newProjection = new String[projection.length];
+ for (int i = 0; i < projection.length; i++) {
+ if (!OWNER_PACKAGE_NAME.equalsIgnoreCase(projection[i])) {
+ newProjection[i] = projection[i];
+ } else {
+ newProjection[i] = constructOwnerPackageNameProjection(queryablePackages);
+ // Allow constructed owner_package_name column in projection
+ final String escapedColumnCase = Pattern.quote(newProjection[i]);
+ qb.getProjectionAllowlist().add(Pattern.compile(escapedColumnCase));
+ }
+ }
+ return newProjection;
+ }
+
+ private String[] maybeReplaceNullProjection(String[] projection, SQLiteQueryBuilder qb) {
+ // List all columns instead of placing "*" in the SQL query
+ // to be able to substitute owner_package_name column
+ if (projection == null) {
+ projection = qb.getAllColumnsFromProjectionMap();
+ // Allow all columns from the projection map
+ qb.setStrictColumns(false);
+ }
+ return projection;
+ }
+
+ @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+ private Set<String> getQueryablePackages(String[] packageNames) {
+ final boolean[] canBeQueriedInfo;
+ try {
+ canBeQueriedInfo = mPackageManager.canPackageQuery(
+ mCallingIdentity.get().getPackageName(), packageNames);
+ } catch (NameNotFoundException e) {
+ Log.e(TAG, "Invalid package name", e);
+ // If package manager throws an error, only assume calling package as queryable package
+ return new HashSet<>(Arrays.asList(mCallingIdentity.get().getPackageName()));
+ }
+
+ final Set<String> queryablePackages = new HashSet<>();
+ for (int i = 0; i < packageNames.length; i++) {
+ if (canBeQueriedInfo[i]) {
+ queryablePackages.add(packageNames[i]);
+ }
+ }
+ return queryablePackages;
+ }
+
@NotNull
private Cursor getReadGrantedMediaForPackage(Bundle extras) {
final int caller = Binder.getCallingUid();
@@ -3860,67 +3974,23 @@
availableVolumes);
}
- @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
- private Set<String> getQueryablePackages(String[] packageNames) {
- final boolean[] canPackageBeQueried;
- try {
- canPackageBeQueried = mPackageManager.canPackageQuery(
- mCallingIdentity.get().getPackageName(), packageNames);
- } catch (NameNotFoundException e) {
- Log.e(TAG, "Invalid package name", e);
- // If package manager throws an error, only assume calling package as queryable package
- return new HashSet<>(Arrays.asList(mCallingIdentity.get().getPackageName()));
- }
-
- final Set<String> queryablePackages = new HashSet<>();
- for (int i = 0; i < packageNames.length; i++) {
- if (canPackageBeQueried[i]) {
- queryablePackages.add(packageNames[i]);
- }
- }
- return queryablePackages;
+ @ChecksSdkIntAtLeast(api = Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+ private boolean shouldFilterOwnerPackageNameInProjection(SQLiteQueryBuilder qb,
+ String[] projection) {
+ return projectionNeedsOwnerPackageFiltering(projection, qb)
+ && isApplicableForOwnerPackageNameFiltering();
}
- private String[] getOwnerPackageNames(Cursor c) {
- final Set<String> ownerPackageNames = new HashSet<>();
- final int ownerPackageNameColIndex = c.getColumnIndex(MediaColumns.OWNER_PACKAGE_NAME);
-
- while (c.moveToNext()) {
- final String ownerPackageName = c.getString(ownerPackageNameColIndex);
- if (!Strings.isNullOrEmpty(ownerPackageName)) {
- ownerPackageNames.add(ownerPackageName);
- }
- }
- c.moveToPosition(-1);
-
- return ownerPackageNames.toArray(new String[0]);
+ private boolean isApplicableForOwnerPackageNameFiltering() {
+ return SdkLevel.isAtLeastU()
+ && getCallingPackageTargetSdkVersion() >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE
+ && !mCallingIdentity.get().checkCallingPermissionsOwnerPackageName();
}
- private Cursor filterOwnerPackageNames(@NonNull Cursor c,
- @NonNull Set<String> queryablePackages) {
- final MatrixCursor filteredCursor = new MatrixCursor(c.getColumnNames(), c.getCount());
-
- while (c.moveToNext()) {
- final MatrixCursor.RowBuilder row = filteredCursor.newRow();
- for (int colIndex = 0; colIndex < c.getColumnCount(); colIndex++) {
- if (OWNER_PACKAGE_NAME.equals(c.getColumnName(colIndex))
- && !queryablePackages.contains(c.getString(colIndex))) {
- row.add(null);
- } else if (c.getType(colIndex) == FIELD_TYPE_BLOB) {
- row.add(c.getBlob(colIndex));
- } else {
- row.add(c.getString(colIndex));
- }
- }
- }
- return filteredCursor;
- }
-
- private boolean isApplicableForOwnerPackageNameFiltering(Cursor c) {
- return SdkLevel.isAtLeastU() && c != null
- && Arrays.asList(c.getColumnNames()).contains(MediaColumns.OWNER_PACKAGE_NAME)
- && getContext().checkPermission(QUERY_ALL_PACKAGES,
- mCallingIdentity.get().pid, mCallingIdentity.get().uid) == PERMISSION_DENIED;
+ private boolean projectionNeedsOwnerPackageFiltering(String[] proj, SQLiteQueryBuilder qb) {
+ return (proj != null && Arrays.asList(proj).contains(MediaColumns.OWNER_PACKAGE_NAME))
+ || (proj == null && qb.getProjectionMap() != null
+ && qb.getProjectionMap().containsKey(OWNER_PACKAGE_NAME));
}
private boolean shouldFilterOwnerPackageNameFlag() {
@@ -6110,14 +6180,15 @@
}
// If caller is an older app, we're willing to let through a
- // greylist of technically invalid columns
+ // allowlist of technically invalid columns
if (getCallingPackageTargetSdkVersion() < Build.VERSION_CODES.Q) {
- qb.setProjectionGreylist(sGreylist);
+ qb.setProjectionAllowlist(sAllowlist);
}
// Starting U, if owner package name is used in query arguments,
// we are restricting result set to only self-owned packages.
- if (shouldFilterOwnerPackageNameFlag() && shouldFilterByOwnerPackageName(extras, type)) {
+ if (shouldFilterOwnerPackageNameFlag()
+ && shouldFilterOwnerPackageNameInSelection(extras, type)) {
Log.d(TAG, "Restricting result set to only packages owned by calling package: "
+ mCallingIdentity.get().getSharedPackagesAsString());
final String ownerPackageMatchClause = getWhereForOwnerPackageMatch(
@@ -6128,10 +6199,9 @@
return qb;
}
- private boolean shouldFilterByOwnerPackageName(Bundle queryArgs, int type) {
- return type == TYPE_QUERY && SdkLevel.isAtLeastU() && containsOwnerPackageName(queryArgs)
- && getContext().checkPermission(QUERY_ALL_PACKAGES, mCallingIdentity.get().pid,
- mCallingIdentity.get().uid) == PERMISSION_DENIED;
+ private boolean shouldFilterOwnerPackageNameInSelection(Bundle queryArgs, int type) {
+ return type == TYPE_QUERY && containsOwnerPackageName(queryArgs)
+ && isApplicableForOwnerPackageNameFiltering();
}
private boolean containsOwnerPackageName(Bundle queryArgs) {
@@ -6657,6 +6727,9 @@
case MediaStore.GET_CLOUD_PROVIDER_CALL: {
return getResultForGetCloudProvider();
}
+ case MediaStore.GET_CLOUD_PROVIDER_LABEL_CALL: {
+ return getResultForGetCloudProviderLabel();
+ }
case MediaStore.SET_CLOUD_PROVIDER_CALL: {
return getResultForSetCloudProvider(extras);
}
@@ -7075,6 +7148,23 @@
}
@NotNull
+ private Bundle getResultForGetCloudProviderLabel() {
+ if (!checkPermissionSystem(Binder.getCallingUid())) {
+ throw new SecurityException(getSecurityExceptionMessage("Get cloud provider label"));
+ }
+ final Bundle res = new Bundle();
+ String cloudProviderLabel = null;
+ try {
+ cloudProviderLabel = mPickerSyncController.getCurrentCloudProviderLocalizedLabel();
+ } catch (UnableToAcquireLockException e) {
+ Log.d(TAG, "Timed out while attempting to acquire the cloud provider lock when getting "
+ + "the cloud provider label.", e);
+ }
+ res.putString(META_DATA_PREFERENCE_SUMMARY, cloudProviderLabel);
+ return res;
+ }
+
+ @NotNull
private Bundle getResultForSetCloudProvider(Bundle extras) {
final String cloudProvider = extras.getString(MediaStore.EXTRA_CLOUD_PROVIDER);
Log.i(TAG, "Request received to set cloud provider to " + cloudProvider);
@@ -10317,7 +10407,8 @@
LocalCallingIdentity localCallingIdentity) {
final File toDelete = new File(path);
if (toDelete.delete()) {
- localCallingIdentity.incrementDeletedFileCountBypassingDatabase();
+ final int mediaType = MimeUtils.resolveMediaType(MimeUtils.resolveMimeType(toDelete));
+ localCallingIdentity.incrementDeletedFileCountBypassingDatabase(mediaType);
return 0;
} else {
return OsConstants.ENOENT;
@@ -11147,6 +11238,7 @@
"Deleting the internal volume is not allowed");
}
+ mDatabaseBackupAndRecovery.onDetachVolume(volume);
// Signal any scanning to shut down
mMediaScanner.onDetachVolume(volume);
@@ -11266,24 +11358,24 @@
/**
* List of abusive custom columns that we're willing to allow via
- * {@link SQLiteQueryBuilder#setProjectionGreylist(List)}.
+ * {@link SQLiteQueryBuilder#setProjectionAllowlist(Collection)}.
*/
- static final ArrayList<Pattern> sGreylist = new ArrayList<>();
+ static final ArrayList<Pattern> sAllowlist = new ArrayList<>();
- private static void addGreylistPattern(String pattern) {
- sGreylist.add(Pattern.compile(" *" + pattern + " *"));
+ private static void addAllowlistPattern(String pattern) {
+ sAllowlist.add(Pattern.compile(" *" + pattern + " *"));
}
static {
final String maybeAs = "( (as )?[_a-z0-9]+)?";
- addGreylistPattern("(?i)[_a-z0-9]+" + maybeAs);
- addGreylistPattern("audio\\._id AS _id");
- addGreylistPattern(
+ addAllowlistPattern("(?i)[_a-z0-9]+" + maybeAs);
+ addAllowlistPattern("audio\\._id AS _id");
+ addAllowlistPattern(
"(?i)(min|max|sum|avg|total|count|cast)\\(([_a-z0-9]+"
+ maybeAs
+ "|\\*)\\)"
+ maybeAs);
- addGreylistPattern(
+ addAllowlistPattern(
"case when case when \\(date_added >= \\d+ and date_added < \\d+\\) then date_added"
+ " \\* \\d+ when \\(date_added >= \\d+ and date_added < \\d+\\) then"
+ " date_added when \\(date_added >= \\d+ and date_added < \\d+\\) then"
@@ -11299,27 +11391,27 @@
+ " \\d+ when \\(date_modified >= \\d+ and date_modified < \\d+\\) then"
+ " date_modified when \\(date_modified >= \\d+ and date_modified < \\d+\\)"
+ " then date_modified / \\d+ else \\d+ end end as corrected_added_modified");
- addGreylistPattern(
+ addAllowlistPattern(
"MAX\\(case when \\(datetaken >= \\d+ and datetaken < \\d+\\) then datetaken \\*"
+ " \\d+ when \\(datetaken >= \\d+ and datetaken < \\d+\\) then datetaken when"
+ " \\(datetaken >= \\d+ and datetaken < \\d+\\) then datetaken / \\d+ else"
+ " \\d+ end\\)");
- addGreylistPattern(
+ addAllowlistPattern(
"MAX\\(case when \\(date_added >= \\d+ and date_added < \\d+\\) then date_added \\*"
+ " \\d+ when \\(date_added >= \\d+ and date_added < \\d+\\) then date_added"
+ " when \\(date_added >= \\d+ and date_added < \\d+\\) then date_added / \\d+"
+ " else \\d+ end\\)");
- addGreylistPattern(
+ addAllowlistPattern(
"MAX\\(case when \\(date_modified >= \\d+ and date_modified < \\d+\\) then"
+ " date_modified \\* \\d+ when \\(date_modified >= \\d+ and date_modified <"
+ " \\d+\\) then date_modified when \\(date_modified >= \\d+ and date_modified"
+ " < \\d+\\) then date_modified / \\d+ else \\d+ end\\)");
- addGreylistPattern("\"content://media/[a-z]+/audio/media\"");
- addGreylistPattern(
+ addAllowlistPattern("\"content://media/[a-z]+/audio/media\"");
+ addAllowlistPattern(
"substr\\(_data, length\\(_data\\)-length\\(_display_name\\), 1\\) as"
+ " filename_prevchar");
- addGreylistPattern("\\*" + maybeAs);
- addGreylistPattern(
+ addAllowlistPattern("\\*" + maybeAs);
+ addAllowlistPattern(
"case when \\(datetaken >= \\d+ and datetaken < \\d+\\) then datetaken \\* \\d+"
+ " when \\(datetaken >= \\d+ and datetaken < \\d+\\) then datetaken when"
+ " \\(datetaken >= \\d+ and datetaken < \\d+\\) then datetaken / \\d+ else"
@@ -11472,10 +11564,8 @@
private void dumpAccessLogs(PrintWriter writer) {
synchronized (mCachedCallingIdentityForFuse) {
- for (int key = 0; key <= mCachedCallingIdentityForFuse.size(); key++) {
- if (mCachedCallingIdentityForFuse.contains(key)) {
- mCachedCallingIdentityForFuse.get(key).dump(writer);
- }
+ for (int i = 0; i < mCachedCallingIdentityForFuse.size(); i++) {
+ mCachedCallingIdentityForFuse.valueAt(i).dump(writer);
}
}
}
diff --git a/src/com/android/providers/media/PickerUriResolver.java b/src/com/android/providers/media/PickerUriResolver.java
index 3a37c9a..0b4fabf 100644
--- a/src/com/android/providers/media/PickerUriResolver.java
+++ b/src/com/android/providers/media/PickerUriResolver.java
@@ -79,6 +79,7 @@
public static final String REFRESH_PICKER_UI_PATH = "refresh_ui";
public static final Uri REFRESH_UI_PICKER_INTERNAL_OBSERVABLE_URI =
PICKER_INTERNAL_URI.buildUpon().appendPath(REFRESH_PICKER_UI_PATH).build();
+ public static final String INIT_PATH = "init";
public static final String MEDIA_PATH = "media";
public static final String ALBUM_PATH = "albums";
diff --git a/src/com/android/providers/media/TranscodeHelperNoOp.java b/src/com/android/providers/media/TranscodeHelperNoOp.java
index 77d3118..03b46f4 100644
--- a/src/com/android/providers/media/TranscodeHelperNoOp.java
+++ b/src/com/android/providers/media/TranscodeHelperNoOp.java
@@ -18,49 +18,61 @@
import android.net.Uri;
import android.os.Bundle;
-import java.io.PrintWriter;
+import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.List;
/**
* No-op transcode helper to avoid loading MediaTranscodeManager classes in Android R
*/
-public class TranscodeHelperNoOp implements TranscodeHelper {
+public final class TranscodeHelperNoOp implements TranscodeHelper {
+
+ @Override
public void freeCache(long bytes) {}
+ @Override
public void onAnrDelayStarted(String packageName, int uid, int tid, int reason) {}
+ @Override
public boolean transcode(String src, String dst, int uid, int reason) {
return false;
}
+ @Override
public String prepareIoPath(String path, int uid) {
return null;
}
+ @Override
public int shouldTranscode(String path, int uid, Bundle bundle) {
return 0;
}
-
+ @Override
public boolean supportsTranscode(String path) {
return false;
}
+ @Override
public void onUriPublished(Uri uri) {}
+ @Override
public void onFileOpen(String path, String ioPath, int uid, int transformsReason) {}
+ @Override
public boolean isTranscodeFileCached(String path, String transcodePath) {
return false;
}
+ @Override
public boolean deleteCachedTranscodeFile(long rowId) {
return false;
}
+ @Override
public void dump(PrintWriter writer) {}
+ @Override
public List<String> getSupportedRelativePaths() {
return new ArrayList<String>();
}
diff --git a/src/com/android/providers/media/fuse/FuseDaemon.java b/src/com/android/providers/media/fuse/FuseDaemon.java
index 84dc593..97e92ce 100644
--- a/src/com/android/providers/media/fuse/FuseDaemon.java
+++ b/src/com/android/providers/media/fuse/FuseDaemon.java
@@ -329,6 +329,19 @@
}
}
+ /**
+ * Removes leveldb connection for given volume name.
+ */
+ public void removeLevelDbConnections(String volumeName) throws IOException {
+ synchronized (mLock) {
+ if (mPtr == 0) {
+ throw new IOException("FUSE daemon unavailable");
+ }
+ native_remove_leveldb_connections(mPtr, volumeName);
+ }
+ }
+
+
private native long native_new(MediaProvider mediaProvider);
// Takes ownership of the passed in file descriptor!
@@ -358,5 +371,6 @@
private native void native_remove_owner_id_relation(long daemon, String ownerId,
String ownerPackageIdentifier);
private native HashMap<String, String> native_read_owner_relations(long daemon);
+ private native void native_remove_leveldb_connections(long daemon, String volumeName);
public static native boolean native_is_fuse_thread();
}
diff --git a/src/com/android/providers/media/metrics/TranscodeMetrics.java b/src/com/android/providers/media/metrics/TranscodeMetrics.java
index 4c4e1d8..3153420 100644
--- a/src/com/android/providers/media/metrics/TranscodeMetrics.java
+++ b/src/com/android/providers/media/metrics/TranscodeMetrics.java
@@ -18,7 +18,6 @@
import static com.android.providers.media.MediaProviderStatsLog.TRANSCODING_DATA;
-import android.app.StatsManager;
import android.util.StatsEvent;
import com.android.internal.annotations.VisibleForTesting;
@@ -44,6 +43,10 @@
// incoming data because of the hard limit on the size.
private static int sTotalStatsDataCount = 0;
+ private TranscodeMetrics() {
+ // Do nothing, this class cannot be instantiated
+ }
+
static List<StatsEvent> pullStatsEvents() {
synchronized (TRANSCODING_STATS_DATA) {
if (TRANSCODING_STATS_DATA.size() > STATS_DATA_SAMPLE_LIMIT) {
diff --git a/src/com/android/providers/media/photopicker/PhotoPickerActivity.java b/src/com/android/providers/media/photopicker/PhotoPickerActivity.java
index f5f6916..6fd8c3e 100644
--- a/src/com/android/providers/media/photopicker/PhotoPickerActivity.java
+++ b/src/com/android/providers/media/photopicker/PhotoPickerActivity.java
@@ -71,11 +71,13 @@
import androidx.lifecycle.ViewModel;
import androidx.lifecycle.ViewModelProvider;
+import com.android.modules.utils.build.SdkLevel;
import com.android.providers.media.ConfigStore;
import com.android.providers.media.R;
import com.android.providers.media.photopicker.data.PickerResult;
import com.android.providers.media.photopicker.data.Selection;
import com.android.providers.media.photopicker.data.UserIdManager;
+import com.android.providers.media.photopicker.data.UserManagerState;
import com.android.providers.media.photopicker.data.model.Item;
import com.android.providers.media.photopicker.data.model.UserId;
import com.android.providers.media.photopicker.ui.TabContainerFragment;
@@ -114,10 +116,12 @@
private View mFragmentContainerView;
private View mDragBar;
private View mProfileButton;
+ private View mProfileMenuButton;
private TextView mPrivacyText;
private TabLayout mTabLayout;
private Toolbar mToolbar;
private CrossProfileListeners mCrossProfileListeners;
+ private ConfigStore mConfigStore;
@NonNull
private final MutableLiveData<Boolean> mIsItemPhotoGridViewChanged =
@@ -171,6 +175,7 @@
mViewModelProvider = new ViewModelProvider(this);
mPickerViewModel = getOrCreateViewModel();
+ mConfigStore = mPickerViewModel.getConfigStore();
final Intent intent = getIntent();
try {
@@ -185,7 +190,7 @@
mPrivacyText = findViewById(R.id.privacy_text);
mBottomBar = findViewById(R.id.picker_bottom_bar);
mProfileButton = findViewById(R.id.profile_button);
-
+ mProfileMenuButton = findViewById(R.id.profile_menu_button);
mTabLayout = findViewById(R.id.tab_layout);
mAccessibilityManager = getSystemService(AccessibilityManager.class);
@@ -440,7 +445,8 @@
private BottomSheetCallback createBottomSheetCallBack() {
return new BottomSheetCallback() {
- private boolean mIsHiddenDueToBottomSheetClosing = false;
+ private boolean mIsProfileButtonHiddenDueToBottomSheetClosing = false;
+ private boolean mIsProfileMenuButtonHiddenDueToBottomSheetClosing = false;
@Override
public void onStateChanged(@NonNull View bottomSheet, int newState) {
if (newState == BottomSheetBehavior.STATE_HIDDEN) {
@@ -459,24 +465,53 @@
// slideOffset = 1 is when bottomsheet is in expanded mode
// We hide the Profile button if the bottomsheet is 50% in between collapsed state
// and hidden state.
- if (slideOffset < HIDE_PROFILE_BUTTON_THRESHOLD &&
- mProfileButton.getVisibility() == View.VISIBLE) {
+ onSlideProfileButton(slideOffset);
+ onSlideProfileMenuButton(slideOffset);
+
+ }
+
+ void onSlideProfileButton(float slideOffset) {
+ // We hide the Profile button if the bottomsheet is 50% in between collapsed state
+ // and hidden state.
+ if (slideOffset < HIDE_PROFILE_BUTTON_THRESHOLD
+ && mProfileButton.getVisibility() == View.VISIBLE) {
mProfileButton.setVisibility(View.GONE);
- mIsHiddenDueToBottomSheetClosing = true;
+ mIsProfileButtonHiddenDueToBottomSheetClosing = true;
return;
}
// We need to handle this state if the user is swiping till the bottom of the
// screen but then swipes up bottom sheet suddenly
- if (slideOffset > HIDE_PROFILE_BUTTON_THRESHOLD &&
- mIsHiddenDueToBottomSheetClosing) {
+ if (slideOffset > HIDE_PROFILE_BUTTON_THRESHOLD
+ && mIsProfileButtonHiddenDueToBottomSheetClosing) {
mProfileButton.setVisibility(View.VISIBLE);
- mIsHiddenDueToBottomSheetClosing = false;
+ mIsProfileButtonHiddenDueToBottomSheetClosing = false;
+ }
+ }
+
+ void onSlideProfileMenuButton(float slideOffset) {
+ if (!(mConfigStore.isPrivateSpaceInPhotoPickerEnabled() && SdkLevel.isAtLeastS())) {
+ return;
+ }
+ if (slideOffset < HIDE_PROFILE_BUTTON_THRESHOLD
+ && mProfileMenuButton.getVisibility() == View.VISIBLE) {
+ mProfileMenuButton.setVisibility(View.GONE);
+ mIsProfileMenuButtonHiddenDueToBottomSheetClosing = true;
+ return;
+ }
+
+ // We need to handle this state if the user is swiping till the bottom of the
+ // screen but then swipes up bottom sheet suddenly
+ if (slideOffset > HIDE_PROFILE_BUTTON_THRESHOLD
+ && mIsProfileMenuButtonHiddenDueToBottomSheetClosing) {
+ mProfileMenuButton.setVisibility(View.VISIBLE);
+ mIsProfileMenuButtonHiddenDueToBottomSheetClosing = false;
}
}
};
}
+
private void setRoundedCornersForBottomSheet() {
final float cornerRadius =
getResources().getDimensionPixelSize(R.dimen.picker_top_corner_radius);
@@ -728,8 +763,10 @@
@UserIdInt
private int getCurrentUserId() {
- final UserIdManager userIdManager = mPickerViewModel.getUserIdManager();
- return userIdManager.getCurrentUserProfileId().getIdentifier();
+ if (mConfigStore.isPrivateSpaceInPhotoPickerEnabled() && SdkLevel.isAtLeastS()) {
+ return mPickerViewModel.getUserManagerState().getCurrentUserProfileId().getIdentifier();
+ }
+ return mPickerViewModel.getUserIdManager().getCurrentUserProfileId().getIdentifier();
}
/**
@@ -753,6 +790,7 @@
if (mode.isPreview) {
mBottomBar.setVisibility(View.GONE);
mProfileButton.setVisibility(View.GONE);
+ mProfileMenuButton.setVisibility(View.GONE);
}
}
@@ -913,13 +951,20 @@
}
/**
+ * Clear all the fragments in the FragmentManager
+ */
+ void clearFragments() {
+ final FragmentManager fragmentManager = getSupportFragmentManager();
+ fragmentManager.popBackStackImmediate(/* name */ null,
+ FragmentManager.POP_BACK_STACK_INCLUSIVE);
+ }
+
+ /**
* Reset to Photo Picker initial launch state (Photos grid tab) in personal profile mode.
*/
private void resetToPersonalProfile() {
// Clear all the fragments in the FragmentManager
- final FragmentManager fragmentManager = getSupportFragmentManager();
- fragmentManager.popBackStackImmediate(/* name */ null,
- FragmentManager.POP_BACK_STACK_INCLUSIVE);
+ clearFragments();
// Reset all content to the personal profile
mPickerViewModel.resetToPersonalProfile();
@@ -929,16 +974,29 @@
}
/**
+ * Reset to Photo Picker initial launch state (Photos grid tab) in user profile mode that
+ * started the photopicker.
+ */
+ private void resetToCurrentUserProfile() {
+ // Clear all the fragments in the FragmentManager
+ clearFragments();
+
+ // Reset all content to the start user profile
+ mPickerViewModel.resetToCurrentUserProfile();
+
+ // Set up the fragments same as the initial launch state
+ setupInitialLaunchState();
+ }
+
+ /**
* Reset to Photo Picker initial launch state (Photos grid tab) in the current profile mode.
*/
- private void resetInCurrentProfile() {
+ private void resetInCurrentProfile(boolean shouldSendInitRequest) {
// Clear all the fragments in the FragmentManager
- final FragmentManager fragmentManager = getSupportFragmentManager();
- fragmentManager.popBackStackImmediate(/* name */ null,
- FragmentManager.POP_BACK_STACK_INCLUSIVE);
+ clearFragments();
// Reset all content in the current profile
- mPickerViewModel.resetAllContentInCurrentProfile();
+ mPickerViewModel.resetAllContentInCurrentProfile(shouldSendInitRequest);
// Set up the fragments same as the initial launch state
setupInitialLaunchState();
@@ -981,8 +1039,7 @@
}
private class CrossProfileListeners {
-
- private final List<String> MANAGED_PROFILE_FILTER_ACTIONS = Lists.newArrayList(
+ private final List<String> mProfileFilterActions = Lists.newArrayList(
Intent.ACTION_MANAGED_PROFILE_ADDED, // add profile button switch
Intent.ACTION_MANAGED_PROFILE_REMOVED, // remove profile button switch
Intent.ACTION_MANAGED_PROFILE_UNLOCKED, // activate profile button switch
@@ -990,8 +1047,17 @@
);
private final UserIdManager mUserIdManager;
+ private final UserManagerState mUserManagerState;
public CrossProfileListeners() {
+ if (mConfigStore.isPrivateSpaceInPhotoPickerEnabled() && SdkLevel.isAtLeastV()) {
+ mProfileFilterActions.add(Intent.ACTION_PROFILE_ADDED);
+ mProfileFilterActions.add(Intent.ACTION_PROFILE_REMOVED);
+ mProfileFilterActions.add(Intent.ACTION_PROFILE_UNAVAILABLE);
+ mProfileFilterActions.add(Intent.ACTION_PROFILE_AVAILABLE);
+ }
+
+ mUserManagerState = mPickerViewModel.getUserManagerState();
mUserIdManager = mPickerViewModel.getUserIdManager();
registerBroadcastReceivers();
@@ -1012,23 +1078,55 @@
// We only need to refresh the layout when the received profile user is the
// managed user corresponding to the current profile or a new work profile is added
// for the current user.
- if (!userId.equals(mUserIdManager.getManagedUserId()) &&
- !action.equals(Intent.ACTION_MANAGED_PROFILE_ADDED)) {
+ if (!(mConfigStore.isPrivateSpaceInPhotoPickerEnabled() && SdkLevel.isAtLeastS())
+ && !userId.equals(mUserIdManager.getManagedUserId())
+ && !action.equals(Intent.ACTION_MANAGED_PROFILE_ADDED)) {
return;
}
+ if (mConfigStore.isPrivateSpaceInPhotoPickerEnabled() && SdkLevel.isAtLeastV()) {
+ switch (action) {
+ case Intent.ACTION_PROFILE_ADDED:
+ handleProfileAdded();
+ break;
+ case Intent.ACTION_PROFILE_REMOVED:
+ handleProfileRemoved(userId);
+ break;
+ case Intent.ACTION_PROFILE_UNAVAILABLE:
+ handleProfileOff(userId);
+ break;
+ case Intent.ACTION_PROFILE_AVAILABLE:
+ handleProfileOn(userId);
+ break;
+ default:
+ // do nothing
+ }
+ }
+
switch (action) {
case Intent.ACTION_MANAGED_PROFILE_UNAVAILABLE:
- handleWorkProfileOff();
+ handleProfileOff(userId);
break;
case Intent.ACTION_MANAGED_PROFILE_REMOVED:
- handleWorkProfileRemoved();
+ if (mConfigStore.isPrivateSpaceInPhotoPickerEnabled()
+ && !SdkLevel.isAtLeastV() && SdkLevel.isAtLeastS()) {
+ handleProfileRemoved(userId);
+ } else if (!(mConfigStore.isPrivateSpaceInPhotoPickerEnabled()
+ && SdkLevel.isAtLeastS())) {
+ handleWorkProfileRemoved();
+ }
break;
case Intent.ACTION_MANAGED_PROFILE_UNLOCKED:
- handleWorkProfileOn();
+ handleProfileOn(userId);
break;
case Intent.ACTION_MANAGED_PROFILE_ADDED:
- handleWorkProfileAdded();
+ if (mConfigStore.isPrivateSpaceInPhotoPickerEnabled()
+ && !SdkLevel.isAtLeastV() && SdkLevel.isAtLeastS()) {
+ handleProfileAdded();
+ } else if (!(mConfigStore.isPrivateSpaceInPhotoPickerEnabled()
+ && SdkLevel.isAtLeastS())) {
+ handleWorkProfileAdded();
+ }
break;
default:
// do nothing
@@ -1037,11 +1135,11 @@
};
private void registerBroadcastReceivers() {
- final IntentFilter managedProfileFilter = new IntentFilter();
- for (String managedProfileAction : MANAGED_PROFILE_FILTER_ACTIONS) {
- managedProfileFilter.addAction(managedProfileAction);
+ final IntentFilter profileFilter = new IntentFilter();
+ for (String profileAction : mProfileFilterActions) {
+ profileFilter.addAction(profileAction);
}
- registerReceiver(mReceiver, managedProfileFilter);
+ registerReceiver(mReceiver, profileFilter);
}
private void handleWorkProfileOff() {
@@ -1050,6 +1148,17 @@
}
mUserIdManager.updateWorkProfileOffValue();
}
+ private void handleProfileOff(UserId userId) {
+ if (mConfigStore.isPrivateSpaceInPhotoPickerEnabled() && SdkLevel.isAtLeastS()) {
+ if (mUserManagerState.isUserSelectedAsCurrentUserProfile(userId)) {
+ switchToCurrentUserProfileInitialLaunchState();
+ }
+ mUserManagerState.updateProfileOffValuesAndPostCrossProfileStatus();
+ return;
+ }
+ handleWorkProfileOff();
+ }
+
private void handleWorkProfileRemoved() {
if (mUserIdManager.isManagedUserSelected()) {
@@ -1058,10 +1167,25 @@
mUserIdManager.resetUserIds();
}
+ private void handleProfileRemoved(UserId userId) {
+ if (mConfigStore.isPrivateSpaceInPhotoPickerEnabled() && SdkLevel.isAtLeastS()) {
+ if (mUserManagerState.isUserSelectedAsCurrentUserProfile(userId)) {
+ switchToCurrentUserProfileInitialLaunchState();
+ }
+ mUserManagerState.resetUserIdsAndSetCrossProfileValues(getIntent());
+ }
+ }
+
private void handleWorkProfileAdded() {
mUserIdManager.resetUserIds();
}
+ private void handleProfileAdded() {
+ if (mConfigStore.isPrivateSpaceInPhotoPickerEnabled() && SdkLevel.isAtLeastS()) {
+ mUserManagerState.resetUserIdsAndSetCrossProfileValues(getIntent());
+ }
+ }
+
private void handleWorkProfileOn() {
// Update UI for switch to profile button
// When the managed profile becomes available, the provider may not be available
@@ -1069,11 +1193,28 @@
mUserIdManager.waitForMediaProviderToBeAvailable();
}
+ private void handleProfileOn(UserId userId) {
+ // Update UI for switch to profile button
+ // When the managed profile becomes available, the provider may not be available
+ // immediately, we need to check if it is ready before we reload the content.
+ if (mConfigStore.isPrivateSpaceInPhotoPickerEnabled() && SdkLevel.isAtLeastS()) {
+ mUserManagerState.waitForMediaProviderToBeAvailable(userId);
+ return;
+ }
+ handleWorkProfileOn();
+ }
+
private void switchToPersonalProfileInitialLaunchState() {
// We reset the state of the PhotoPicker as we do not want to make any
// assumptions on the state of the PhotoPicker when it was in Work Profile mode.
resetToPersonalProfile();
}
+
+ private void switchToCurrentUserProfileInitialLaunchState() {
+ // We reset the state of the PhotoPicker as we do not want to make any
+ // assumptions on the state of the PhotoPicker when it was in other Profile mode.
+ resetToCurrentUserProfile();
+ }
}
/**
@@ -1088,14 +1229,15 @@
}
/**
- * Reset the Picker view model content when launched with cloud features and notified to
+ * Reset the picker view model content when launched with cloud features and notified to
* refresh the UI.
*/
private void observeRefreshUiNotificationLiveData() {
- mPickerViewModel.shouldRefreshUiLiveData()
- .observe(this, shouldRefresh -> {
- if (shouldRefresh && !mPickerViewModel.shouldShowOnlyLocalFeatures()) {
- resetInCurrentProfile();
+ mPickerViewModel.refreshUiLiveData()
+ .observe(this, refreshRequest -> {
+ if (refreshRequest.shouldRefreshPicker()
+ && !mPickerViewModel.shouldShowOnlyLocalFeatures()) {
+ resetInCurrentProfile(refreshRequest.shouldInitPicker());
}
});
}
diff --git a/src/com/android/providers/media/photopicker/PhotoPickerSettingsActivity.java b/src/com/android/providers/media/photopicker/PhotoPickerSettingsActivity.java
index 49d6e3d..4845ab1 100644
--- a/src/com/android/providers/media/photopicker/PhotoPickerSettingsActivity.java
+++ b/src/com/android/providers/media/photopicker/PhotoPickerSettingsActivity.java
@@ -33,8 +33,11 @@
import androidx.fragment.app.FragmentManager;
import androidx.lifecycle.ViewModelProvider;
+import com.android.modules.utils.build.SdkLevel;
import com.android.providers.media.R;
import com.android.providers.media.photopicker.data.UserIdManager;
+import com.android.providers.media.photopicker.data.UserManagerState;
+import com.android.providers.media.photopicker.data.model.UserId;
import com.android.providers.media.photopicker.ui.settings.SettingsProfileSelectFragment;
import com.android.providers.media.photopicker.ui.settings.SettingsViewModel;
@@ -116,23 +119,44 @@
// Target fragment is SettingsProfileSelectFragment if there exists more than one
// UserHandles for profiles associated with the context user, including the user itself.
// Else target fragment is SettingsCloudMediaSelectFragment
- final UserIdManager userIdManager = mSettingsViewModel.getUserIdManager();
- if (userIdManager.isMultiUserProfiles()) {
- // In case work profile exists and is turned off, do not show the work tab.
- userIdManager.updateWorkProfileOffValue();
- if (!userIdManager.isWorkProfileOff()) {
- final int selectedProfileTab = getInitialProfileTab(callingUserId);
- return SettingsProfileSelectFragment.getProfileSelectFragment(selectedProfileTab);
+ boolean showPersonalAndWorkTabs = false;
+ if (mSettingsViewModel.getConfigStore().isPrivateSpaceInPhotoPickerEnabled()
+ && SdkLevel.isAtLeastS()) {
+ final UserManagerState userManagerState = mSettingsViewModel.getUserManagerState();
+ if (userManagerState.isMultiUserProfiles()) {
+ userManagerState.updateProfileOffValues();
+ for (UserId userId : userManagerState.getAllUserProfileIds()) {
+ // In case work profile exists and is turned off, do not show the work tab.
+ if (userManagerState.isManagedUserProfile(userId)
+ && !userManagerState.isProfileOff(userId)) {
+ showPersonalAndWorkTabs = true;
+ break;
+ }
+ }
+ }
+ } else {
+ final UserIdManager userIdManager = mSettingsViewModel.getUserIdManager();
+ if (userIdManager.isMultiUserProfiles()) {
+ userIdManager.updateWorkProfileOffValue();
+ // In case work profile exists and is turned off, do not show the work tab.
+ if (!userIdManager.isWorkProfileOff()) {
+ showPersonalAndWorkTabs = true;
+ }
}
}
+
+ if (showPersonalAndWorkTabs) {
+ final int selectedProfileTab = getInitialProfileTab(callingUserId);
+ return SettingsProfileSelectFragment.getProfileSelectFragment(selectedProfileTab);
+ }
+
return getCloudMediaSelectFragment();
}
@NonNull
private Fragment getCloudMediaSelectFragment() {
- final UserIdManager userIdManager = mSettingsViewModel.getUserIdManager();
- final int userId = userIdManager.getCurrentUserProfileId().getIdentifier();
- return SettingsProfileSelectFragment.getCloudMediaSelectFragment(userId);
+ return SettingsProfileSelectFragment.getCloudMediaSelectFragment(
+ UserId.CURRENT_USER.getIdentifier());
}
/**
diff --git a/src/com/android/providers/media/photopicker/PickerDataLayer.java b/src/com/android/providers/media/photopicker/PickerDataLayer.java
index 49b3d4b..86e2324 100644
--- a/src/com/android/providers/media/photopicker/PickerDataLayer.java
+++ b/src/com/android/providers/media/photopicker/PickerDataLayer.java
@@ -521,7 +521,8 @@
if (!syncRequestExtras.shouldSyncMergedAlbum()) {
mSyncManager.syncAlbumMediaForProviderImmediately(
syncRequestExtras.getAlbumId(),
- syncRequestExtras.getAlbumAuthority());
+ syncRequestExtras.getAlbumAuthority(),
+ isLocal(syncRequestExtras.getAlbumAuthority()));
}
}
}
diff --git a/src/com/android/providers/media/photopicker/PickerSyncController.java b/src/com/android/providers/media/photopicker/PickerSyncController.java
index eb7df4f..0e69876 100644
--- a/src/com/android/providers/media/photopicker/PickerSyncController.java
+++ b/src/com/android/providers/media/photopicker/PickerSyncController.java
@@ -26,6 +26,7 @@
import static android.provider.CloudMediaProviderContract.MediaCollectionInfo.MEDIA_COLLECTION_ID;
import static android.provider.MediaStore.MY_UID;
+import static com.android.providers.media.PickerUriResolver.INIT_PATH;
import static com.android.providers.media.PickerUriResolver.PICKER_INTERNAL_URI;
import static com.android.providers.media.PickerUriResolver.REFRESH_UI_PICKER_INTERNAL_OBSERVABLE_URI;
import static com.android.providers.media.PickerUriResolver.getDeletedMediaUri;
@@ -62,6 +63,7 @@
import com.android.modules.utils.BackgroundThread;
import com.android.modules.utils.build.SdkLevel;
import com.android.providers.media.ConfigStore;
+import com.android.providers.media.R;
import com.android.providers.media.photopicker.data.CloudProviderInfo;
import com.android.providers.media.photopicker.data.PickerDbFacade;
import com.android.providers.media.photopicker.metrics.NonUiEventLogger;
@@ -560,6 +562,22 @@
return mLocalProvider;
}
+ /**
+ * @return current cloud provider app localized label. This operation acquires a lock
+ * internally with a timeout.
+ * @throws UnableToAcquireLockException if the lock was not acquired within the given timeout.
+ */
+ public String getCurrentCloudProviderLocalizedLabel() throws UnableToAcquireLockException {
+ try (CloseableReentrantLock ignored = mPickerSyncLockManager
+ .tryLock(PickerSyncLockManager.CLOUD_PROVIDER_LOCK)) {
+ if (mCloudProviderInfo.isEmpty()) {
+ return mContext.getResources().getString(R.string.picker_settings_no_provider);
+ }
+ return CloudProviderUtils.getProviderLabel(
+ mContext.getPackageManager(), mCloudProviderInfo.authority);
+ }
+ }
+
public boolean isProviderEnabled(String authority) {
if (mLocalProvider.equals(authority)) {
return true;
@@ -723,7 +741,7 @@
// Send UI refresh notification for any active picker sessions, as the
// UI data might be stale if a full sync needs to be run.
- sendPickerUiRefreshNotification();
+ sendPickerUiRefreshNotification(/* isInitPending */ false);
final Bundle fullSyncQueryArgs = new Bundle();
if (enablePagedSync) {
@@ -1050,14 +1068,27 @@
Log.wtf(TAG, "CLOUD_PROVIDER_LOCK is already held by this thread.");
}
- sendPickerUiRefreshNotification();
+ sendPickerUiRefreshNotification(/* isInitPending */ true);
}
}
- private void sendPickerUiRefreshNotification() {
- ContentResolver contentResolver = mContext.getContentResolver();
+ /**
+ * Send Picker UI content observers a notification that a refresh is required.
+ * @param isInitPending when true, appends the URI path segment
+ * {@link com.android.providers.media.PickerUriResolver.INIT_PATH} to the notification URI
+ * to indicate that the UI that the cached picker data might be stale.
+ * When a request notification is being sent from the sync path, set isInitPending as false to
+ * prevent sending refresh notification in a loop.
+ */
+ private void sendPickerUiRefreshNotification(boolean isInitPending) {
+ final ContentResolver contentResolver = mContext.getContentResolver();
if (contentResolver != null) {
- contentResolver.notifyChange(REFRESH_UI_PICKER_INTERNAL_OBSERVABLE_URI, null);
+ final Uri.Builder builder = REFRESH_UI_PICKER_INTERNAL_OBSERVABLE_URI.buildUpon();
+ if (isInitPending) {
+ builder.appendPath(INIT_PATH);
+ }
+ final Uri refreshUri = builder.build();
+ contentResolver.notifyChange(refreshUri, null);
} else {
Log.d(TAG, "Couldn't notify the Picker UI to refresh");
}
diff --git a/src/com/android/providers/media/photopicker/SelectedMediaPreloader.java b/src/com/android/providers/media/photopicker/SelectedMediaPreloader.java
index deefc1b..f6eb626 100644
--- a/src/com/android/providers/media/photopicker/SelectedMediaPreloader.java
+++ b/src/com/android/providers/media/photopicker/SelectedMediaPreloader.java
@@ -47,13 +47,9 @@
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
-import java.util.concurrent.CompletableFuture;
-import java.util.concurrent.ExecutionException;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadFactory;
-import java.util.concurrent.TimeUnit;
-import java.util.concurrent.TimeoutException;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference;
@@ -262,23 +258,14 @@
Trace.beginSection("Preloader.openFd");
- CompletableFuture<Void> future = CompletableFuture.runAsync(() -> {
- try {
- mContentResolver.openAssetFileDescriptor(uri, "r").close();
- } catch (FileNotFoundException e) {
- isOpenedSuccessfully.set(false);
- Log.w(TAG, "Could not open FileDescriptor for " + uri, e);
- } catch (IOException e) {
- Log.w(TAG, "Failed to preload media file ", e);
- }
- });
-
try {
- future.get(TIMEOUT_IN_SECONDS, TimeUnit.SECONDS);
- } catch (TimeoutException e) {
- return isOpenedSuccessfully.get();
- } catch (InterruptedException | ExecutionException e) {
- Log.w(TAG, "Could not preload the media item ", e);
+ mContentResolver.openAssetFileDescriptor(uri, "r").close();
+ } catch (FileNotFoundException e) {
+ isOpenedSuccessfully.set(false);
+ Log.w(TAG, "Could not open FileDescriptor for " + uri, e);
+ } catch (IOException e) {
+ isOpenedSuccessfully.set(false);
+ Log.w(TAG, "Failed to preload media file ", e);
} finally {
Trace.endSection();
diff --git a/src/com/android/providers/media/photopicker/data/UserManagerState.java b/src/com/android/providers/media/photopicker/data/UserManagerState.java
new file mode 100644
index 0000000..c8157f0
--- /dev/null
+++ b/src/com/android/providers/media/photopicker/data/UserManagerState.java
@@ -0,0 +1,717 @@
+/*
+ * Copyright (C) 2021 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.providers.media.photopicker.data;
+
+import static androidx.core.util.Preconditions.checkNotNull;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.RequiresApi;
+import android.app.ActivityManager;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.content.pm.UserProperties;
+import android.content.res.Resources;
+import android.graphics.drawable.Drawable;
+import android.os.Build;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.UserHandle;
+import android.os.UserManager;
+import android.util.Log;
+
+import androidx.lifecycle.MutableLiveData;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.modules.utils.build.SdkLevel;
+import com.android.providers.media.R;
+import com.android.providers.media.photopicker.data.model.UserId;
+import com.android.providers.media.photopicker.ui.TabFragment;
+import com.android.providers.media.photopicker.util.CrossProfileUtils;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+@RequiresApi(Build.VERSION_CODES.S)
+/*
+ * Interface to query user ids {@link UserId}
+ */
+public interface UserManagerState {
+ /**
+ * Whether there are more than 1 user profiles associated with the current user.
+ */
+ boolean isMultiUserProfiles();
+
+ /**
+ * @return if the given userId is a ManagedUserProfileId.
+ */
+ boolean isManagedUserProfile(UserId userId);
+
+ /**
+ * Returns the user profile selected in the photopicker. If the user does not have a
+ * corresponding child profile, then this always returns the current user.
+ */
+ @Nullable
+ UserId getCurrentUserProfileId();
+
+ /**
+ * A Map of all the profiles with their cross profile allowed status from current user.
+ * key : userId of a profile
+ * Value : cross profile allowed status of a user profile corresponding to user id with
+ * current user .
+ */
+ @NonNull
+ Map<UserId, Boolean> getCrossProfileAllowedStatusForAll();
+
+ /**
+ * Get total number of profiles with {@link UserProperties.SHOW_IN_SHARING_SURFACES_SEPARATE}
+ * available on the device
+ */
+ int getProfileCount();
+
+ /**
+ *
+ * A {@link MutableLiveData} to check if cross profile interaction allowed or not.
+ */
+ @NonNull
+ MutableLiveData<Map<UserId, Boolean>> getCrossProfileAllowed();
+
+ /**
+ * A list of all user profile ids including current user that need to be shown
+ * separately in PhotoPicker
+ */
+ @NonNull
+ List<UserId> getAllUserProfileIds();
+
+ /**
+ * Updates on/off values of all the user profiles and post cross profile status of all profiles
+ */
+ void updateProfileOffValuesAndPostCrossProfileStatus();
+
+ /**
+ * Updates on/off values of all the user profiles
+ */
+ void updateProfileOffValues();
+
+ /**
+ * Waits for Media Provider of the user profile corresponding to userId to be available.
+ */
+ void waitForMediaProviderToBeAvailable(UserId userId);
+
+ /**
+ * Get if it is allowed to access the otherUser profile from current user ( current user :
+ * the user profile that started the photo picker activity)
+ **/
+ @NonNull
+ boolean isCrossProfileAllowedToUser(UserId otherUser);
+
+ /**
+ * A {@link MutableLiveData} to check if there are multiple user profiles or not
+ */
+ @NonNull
+ MutableLiveData<Boolean> getIsMultiUserProfiles();
+
+ /**
+ * Resets the user ids. This is usually called as a result of receiving broadcast that
+ * any profile has been added or removed.
+ */
+ void resetUserIds();
+
+ /**
+ * Resets the user ids and set their cross profile values. This is usually called as a result
+ * of receiving broadcast that any profile has been added or removed.
+ */
+ void resetUserIdsAndSetCrossProfileValues(Intent intent);
+
+ /**
+ * @return true if current user is the current user profile selected
+ */
+ boolean isCurrentUserSelected();
+
+ /**
+ * Checks device admin policy for cross-profile sharing for intent. It Also updates profile off
+ * and blocked by admin status for all user profiles present on the device.
+ */
+ void setIntentAndCheckRestrictions(Intent intent);
+
+ /**
+ * Whether cross profile access corresponding to the userID is blocked
+ * by admin for the current user.
+ */
+ boolean isBlockedByAdmin(UserId userId);
+
+ /**
+ * Whether profile corresponding to the userID is on or off.
+ */
+ boolean isProfileOff(UserId userId);
+
+ /**
+ * A map of all user profile labels corresponding to all profile userIds
+ */
+ Map<UserId, String> getProfileLabelsForAll();
+
+ /**
+ * Returns whether a user should be shown in the PhotoPicker depending on its quite mode status.
+ *
+ * @return One of {@link UserProperties.SHOW_IN_QUIET_MODE_PAUSED},
+ * {@link UserProperties.SHOW_IN_QUIET_MODE_HIDDEN}, or
+ * {@link UserProperties.SHOW_IN_QUIET_MODE_DEFAULT} depending on whether the profile
+ * should be shown in quiet mode or not.
+ */
+ int getShowInQuietMode(UserId userId);
+
+ /**
+ * A map of all user profile Icon ids corresponding to all profile userIds
+ */
+ Map<UserId, Drawable> getProfileBadgeForAll();
+
+ /**
+ * Set a user as a current user profile
+ **/
+ void setUserAsCurrentUserProfile(UserId userId);
+
+ /**
+ * @return true if provided user is the current user profile selected
+ */
+ boolean isUserSelectedAsCurrentUserProfile(UserId userId);
+
+ /**
+ * Creates an implementation of {@link UserManagerState}.
+ * Todo(b/319067964): make this singleton
+ */
+ static UserManagerState create(Context context) {
+ return new RuntimeUserManagerState(context);
+ }
+
+ /**
+ * Implementation of {@link UserManagerState}. The class assumes that all its public methods are
+ * called from main thread only.
+ */
+ final class RuntimeUserManagerState implements UserManagerState {
+
+ private static final String TAG = "UserManagerState";
+ private static final int PROVIDER_AVAILABILITY_MAX_RETRIES = 10;
+ private static final long PROVIDER_AVAILABILITY_CHECK_DELAY = 4000;
+ private static final int SHOW_IN_QUIET_MODE_DEFAULT = -1;
+
+ private final Context mContext;
+ // This is the user profile that started the photo picker activity. That's why it cannot
+ // change in a UserIdManager instance.
+ private final UserId mCurrentUser;
+ private final Handler mHandler;
+
+ private Runnable mIsProviderAvailableRunnable;
+
+ // This is the user profile selected in the photo picker. Photo picker will display media
+ // for this user. It could be different from mCurrentUser.
+ private UserId mCurrentUserProfile = null;
+
+ // A map of user profile ids (Except current user) with a Boolean value that represents
+ // whether corresponding user profile is blocked by admin or not.
+ private Map<UserId , Boolean> mIsProfileBlockedByAdminMap = new HashMap<>();
+
+ // A map of user profile ids (Except current user) with a Boolean value that represents
+ // whether corresponding user profile is on or off.
+ private Map<UserId , Boolean> mProfileOffStatus = new HashMap<>();
+ private final MutableLiveData<Boolean> mIsMultiUserProfiles = new MutableLiveData<>();
+
+ // A list of all user profile Ids present on the device that require a separate tab to show
+ // in PhotoPicker. It also includes currentUser/contextUser.
+ private List<UserId> mUserProfileIds = new ArrayList<>();
+ private UserManager mUserManager;
+
+ /**
+ * This live data will be posted every time when a user profile change occurs in the
+ * background such as turning on/off/adding/removing a user profile. The complete map
+ * will be reinitiated again in {@link #getCrossProfileAllowedStatusForAll()} and will
+ * be posted into the below mutable live data. This live data will be observed later in
+ * {@link TabFragment}.
+ **/
+ private final MutableLiveData<Map<UserId, Boolean>> mCrossProfileAllowedStatus =
+ new MutableLiveData<>();
+
+ private RuntimeUserManagerState(Context context) {
+ this(context, UserId.CURRENT_USER);
+ }
+
+ @VisibleForTesting
+ RuntimeUserManagerState(Context context, UserId currentUser) {
+ mContext = context.getApplicationContext();
+ mCurrentUser = checkNotNull(currentUser);
+ mCurrentUserProfile = mCurrentUser;
+ mHandler = new Handler(Looper.getMainLooper());
+ mUserManager = mContext.getSystemService(UserManager.class);
+ setUserIds();
+ }
+
+ private UserId getSystemUser() {
+ return UserId.of(UserHandle.of(ActivityManager.getCurrentUser()));
+ }
+
+ private void setUserIds() {
+ setUserIdsInternal();
+ mIsMultiUserProfiles.postValue(isMultiUserProfiles());
+ }
+
+ private void setUserIdsInternal() {
+ mUserProfileIds.clear();
+ mUserProfileIds.add(getSystemUser());
+ if (mUserManager == null) {
+ Log.e(TAG, "Cannot obtain user manager");
+ return;
+ }
+
+ // Here there could be other profiles too , that we do not want to show anywhere in
+ // photo picker at all.
+ final List<UserHandle> userProfiles = mUserManager.getUserProfiles();
+ if (SdkLevel.isAtLeastV()) {
+ for (UserHandle userHandle : userProfiles) {
+ UserProperties userProperties = mUserManager.getUserProperties(userHandle);
+ UserId userId = UserId.of(userHandle);
+
+ // Check if we want to show this profile data in PhotoPicker or if it is
+ // an owner profile itself.
+ if (getSystemUser().getIdentifier() != userHandle.getIdentifier()
+ && userProperties.getShowInSharingSurfaces()
+ == userProperties.SHOW_IN_SHARING_SURFACES_SEPARATE) {
+ mUserProfileIds.add(userId);
+ }
+ }
+ } else {
+ // if sdk version is less than V, then maximum two profiles with separate tab could
+ // only be available
+ for (UserHandle userHandle : userProfiles) {
+ if (mUserManager.isManagedProfile(userHandle.getIdentifier())) {
+ mUserProfileIds.add(UserId.of(userHandle));
+ }
+ }
+ }
+ }
+
+ @Override
+ public boolean isMultiUserProfiles() {
+ assertMainThread();
+ return mUserProfileIds.size() > 1;
+ }
+
+ @Override
+ public int getProfileCount() {
+ return mUserProfileIds.size();
+ }
+
+ @Override
+ public MutableLiveData<Map<UserId, Boolean>> getCrossProfileAllowed() {
+ return mCrossProfileAllowedStatus;
+ }
+
+ @Override
+ public Map<UserId, Boolean> getCrossProfileAllowedStatusForAll() {
+ assertMainThread();
+ Map<UserId, Boolean> crossProfileAllowedStatusForAll = new HashMap<>();
+ for (UserId userId : mUserProfileIds) {
+ crossProfileAllowedStatusForAll.put(userId, isCrossProfileAllowedToUser(userId));
+ }
+ return crossProfileAllowedStatusForAll;
+ }
+
+ @Override
+ public boolean isCrossProfileAllowedToUser(UserId otherUser) {
+ assertMainThread();
+ return !isProfileOff(otherUser) && !isBlockedByAdmin(otherUser);
+ }
+
+ @Override
+ public MutableLiveData<Boolean> getIsMultiUserProfiles() {
+ return mIsMultiUserProfiles;
+ }
+
+ @Override
+ public void resetUserIds() {
+ assertMainThread();
+ setUserIds();
+ }
+
+ @Override
+ public void resetUserIdsAndSetCrossProfileValues(Intent intent) {
+ assertMainThread();
+ setUserIdsInternal();
+ setCrossProfileValues(intent);
+ mIsMultiUserProfiles.postValue(isMultiUserProfiles());
+ }
+
+ @Override
+ public boolean isCurrentUserSelected() {
+ assertMainThread();
+ return mCurrentUserProfile.equals(UserId.CURRENT_USER);
+ }
+
+ @Override
+ public void setIntentAndCheckRestrictions(Intent intent) {
+ assertMainThread();
+ // The below method should be called even if only one profile is present on the device
+ // because we want to have current profile off value and blocked by admin values in the
+ // corresponding maps
+ updateCrossProfileValues(intent);
+
+ }
+
+ @Override
+ public UserId getCurrentUserProfileId() {
+ assertMainThread();
+ return mCurrentUserProfile;
+ }
+
+ /**
+ * we need the information of personal and managed user to get the pre-exiting label and
+ * icon of user profile ids in case while working with pre-v version.
+ */
+ @Override
+ public boolean isManagedUserProfile(UserId userId) {
+ assertMainThread();
+ if (mUserManager == null) {
+ Log.e(TAG, "Cannot obtain user manager");
+ return false;
+ }
+ return userId.isManagedProfile(mUserManager);
+ }
+
+ @Override
+ public void setUserAsCurrentUserProfile(UserId userId) {
+ assertMainThread();
+ if (!mUserProfileIds.contains(userId)) {
+ Log.e(TAG, userId + " is not a valid user profile");
+ return;
+ }
+ setCurrentUserProfileId(userId);
+ }
+
+ @Override
+ public boolean isUserSelectedAsCurrentUserProfile(UserId userId) {
+ assertMainThread();
+ return mCurrentUserProfile.equals(userId);
+ }
+
+ private void setCurrentUserProfileId(UserId userId) {
+ mCurrentUserProfile = userId;
+ }
+
+ private void updateCrossProfileValues(Intent intent) {
+ setCrossProfileValues(intent);
+ updateAndPostCrossProfileStatus();
+ }
+
+ private void setCrossProfileValues(Intent intent) {
+ // 1. Check if PICK_IMAGES intent is allowed by admin to show cross user content
+ setBlockedByAdminValue(intent);
+
+ // 2. Check if work profile is off
+ updateProfileOffValuesAndPostCrossProfileStatus();
+
+ // 3. For first initial setup, wait for MediaProvider to be on.
+ // (This is not blocking)
+ for (UserId userId : mUserProfileIds) {
+ if (mProfileOffStatus.get(userId)) {
+ waitForMediaProviderToBeAvailable(userId);
+ }
+ }
+ }
+ @Override
+ public void waitForMediaProviderToBeAvailable(UserId userId) {
+ assertMainThread();
+ if (CrossProfileUtils.isMediaProviderAvailable(userId , mContext)) {
+ mProfileOffStatus.put(userId, false);
+ updateAndPostCrossProfileStatus();
+ return;
+ }
+ waitForProviderToBeAvailable(userId, /* numOfTries */ 1);
+ }
+
+ private void waitForProviderToBeAvailable(UserId userId, int numOfTries) {
+ // The runnable should make sure to post update on the live data if it is changed.
+ mIsProviderAvailableRunnable = () -> {
+ // We stop the recursive check when
+ // 1. the provider is available
+ // 2. the profile is in quiet mode, i.e. provider will not be available
+ // 3. after maximum retries
+ if (CrossProfileUtils.isMediaProviderAvailable(userId, mContext)) {
+ mProfileOffStatus.put(userId, false);
+ updateAndPostCrossProfileStatus();
+ return;
+ }
+
+ if (CrossProfileUtils.isQuietModeEnabled(userId, mContext)) {
+ return;
+ }
+
+ if (numOfTries <= PROVIDER_AVAILABILITY_MAX_RETRIES) {
+ Log.d(TAG, "MediaProvider is not available. Retry after "
+ + PROVIDER_AVAILABILITY_CHECK_DELAY);
+ waitForProviderToBeAvailable(userId, numOfTries + 1);
+ return;
+ }
+
+ Log.w(TAG, "Failed waiting for MediaProvider for user:" + userId
+ + " to be available");
+ };
+
+ mHandler.postDelayed(mIsProviderAvailableRunnable, PROVIDER_AVAILABILITY_CHECK_DELAY);
+ }
+
+ // Todo(b/319561515): Modify method to remove callbacks only for specified user
+ private void stopWaitingForProviderToBeAvailable() {
+ if (mIsProviderAvailableRunnable == null) {
+ return;
+ }
+ mHandler.removeCallbacks(mIsProviderAvailableRunnable);
+ mIsProviderAvailableRunnable = null;
+ }
+
+ @Override
+ public void updateProfileOffValuesAndPostCrossProfileStatus() {
+ updateProfileOffValues();
+ updateAndPostCrossProfileStatus();
+ }
+
+ @Override
+ public void updateProfileOffValues() {
+ assertMainThread();
+ mProfileOffStatus.clear();
+ for (UserId userId : mUserProfileIds) {
+ mProfileOffStatus.put(userId, isProfileOffInternal(userId));
+ }
+ }
+
+ private void updateAndPostCrossProfileStatus() {
+ mCrossProfileAllowedStatus.postValue(getCrossProfileAllowedStatusForAll());
+ }
+
+ private Boolean isProfileOffInternal(UserId userId) {
+ return CrossProfileUtils.isQuietModeEnabled(userId, mContext)
+ || !CrossProfileUtils.isMediaProviderAvailable(userId, mContext);
+ }
+
+ private boolean isCrossProfileStrategyDelegatedToParent(UserHandle userHandle) {
+ if (SdkLevel.isAtLeastV()) {
+ if (mUserManager == null) {
+ Log.e(TAG, "Cannot obtain user manager");
+ return false;
+ }
+ UserProperties userProperties = mUserManager.getUserProperties(userHandle);
+ if (userProperties.getCrossProfileContentSharingStrategy()
+ == userProperties.CROSS_PROFILE_CONTENT_SHARING_DELEGATE_FROM_PARENT) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ private UserHandle getProfileToCheckCrossProfileAccess(UserHandle userHandle) {
+ if (mUserManager == null) {
+ Log.e(TAG, "Cannot obtain user manager");
+ return null;
+ }
+ return isCrossProfileStrategyDelegatedToParent(userHandle)
+ ? mUserManager.getProfileParent(userHandle) : userHandle;
+ }
+
+
+ /**
+ * {@link #setBlockedByAdminValue(Intent)} Based on assumption that the only profiles with
+ * {@link UserProperties.CROSS_PROFILE_CONTENT_SHARING_NO_DELEGATION} could be systemUser
+ * and managedUser(if available).
+ *
+ * Todo(b/319567023):Refactor the below {@link #setBlockedByAdminValue(Intent)} to
+ * avoid assumptions mentioned above.
+ */
+ private void setBlockedByAdminValue(Intent intent) {
+ if (intent == null) {
+ Log.e(TAG, "No intent specified to check if cross profile forwarding is"
+ + " allowed.");
+ return;
+ }
+
+ // List of all user profile ids that context user cannot access
+ List<UserId> canNotForwardToUserProfiles = new ArrayList<>();
+
+ /*
+ * List of all user profile ids that have cross profile access among themselves.
+ * It contains parent user and child profiles with user property
+ * {@link UserProperties.CROSS_PROFILE_CONTENT_SHARING_DELEGATE_FROM_PARENT}
+ */
+ List<UserId> parentOrDelegatedFromParent = new ArrayList<>();
+
+ // Userprofile to check cross profile intentForwarderActivity for
+ UserHandle needToCheck = null;
+
+ if (mUserManager == null) {
+ Log.e(TAG, "Cannot obtain user manager");
+ return;
+ }
+
+ for (UserId userId : mUserProfileIds) {
+ /*
+ * All user profiles with user property
+ * {@link UserProperties.CROSS_PROFILE_CONTENT_SHARING_DELEGATE_FROM_PARENT}
+ * can access each other including its parent.
+ */
+ if (userId.equals(getSystemUser())
+ || isCrossProfileStrategyDelegatedToParent(userId.getUserHandle())) {
+ parentOrDelegatedFromParent.add(userId);
+ } else {
+ needToCheck = userId.getUserHandle();
+ }
+ }
+
+ // When context user is a managed user , then will replace needToCheck with its parent
+ // to check cross profile intentForwarderActivity for.
+ if (needToCheck != null && needToCheck.equals(mCurrentUser.getUserHandle())) {
+ needToCheck = mUserManager.getProfileParent(mCurrentUser.getUserHandle());
+ }
+
+ final PackageManager packageManager = mContext.getPackageManager();
+ if (needToCheck != null && !CrossProfileUtils.isIntentAllowedCrossProfileAccessFromUser(
+ intent, packageManager,
+ getProfileToCheckCrossProfileAccess(mCurrentUser.getUserHandle()))) {
+ if (parentOrDelegatedFromParent.contains(UserId.of(needToCheck))) {
+ // if user profile cannot access its parent then all direct child profiles with
+ // delegated from parent will also be inaccessible.
+ canNotForwardToUserProfiles.addAll(parentOrDelegatedFromParent);
+ } else {
+ canNotForwardToUserProfiles.add(UserId.of(needToCheck));
+ }
+ }
+
+ mIsProfileBlockedByAdminMap.clear();
+ for (UserId userId : mUserProfileIds) {
+ mIsProfileBlockedByAdminMap.put(userId,
+ canNotForwardToUserProfiles.contains(userId));
+ }
+ }
+
+ @Override
+ public Map<UserId, String> getProfileLabelsForAll() {
+ assertMainThread();
+ Map<UserId, String> profileLabels = new HashMap<>();
+ String personalTabLabel = mContext.getString(R.string.picker_personal_profile_label);
+ profileLabels.put(getSystemUser(), personalTabLabel);
+ if (SdkLevel.isAtLeastV()) {
+ for (UserId userId : mUserProfileIds) {
+ UserHandle userHandle = userId.getUserHandle();
+ if (userHandle.getIdentifier() != getSystemUser().getIdentifier()) {
+ profileLabels.put(userId, getProfileLabel(userHandle));
+ }
+ }
+ }
+
+ return profileLabels;
+ }
+ private String getProfileLabel(UserHandle userHandle) {
+ if (SdkLevel.isAtLeastV()) {
+ Context userContext = mContext.createContextAsUser(userHandle, 0 /* flags */);
+ try {
+ UserManager userManager = userContext.getSystemService(UserManager.class);
+ if (userManager == null) {
+ Log.e(TAG, "Cannot obtain user manager");
+ return null;
+ }
+ return userManager.getProfileLabel();
+ } catch (Resources.NotFoundException e) {
+ //Todo(b/318530691): Handle the label for the profile that is not defined
+ // already
+ }
+ }
+ return null;
+ }
+
+ @Override
+ public Map<UserId, Drawable> getProfileBadgeForAll() {
+ assertMainThread();
+ Map<UserId, Drawable> profileBadges = new HashMap<>();
+ profileBadges.put(getSystemUser(), mContext.getDrawable(R.drawable.ic_personal_mode));
+ if (SdkLevel.isAtLeastV()) {
+ for (UserId userId : mUserProfileIds) {
+ UserHandle userHandle = userId.getUserHandle();
+ if (userHandle.getIdentifier() != getSystemUser().getIdentifier()) {
+ profileBadges.put(userId, getProfileBadge(userHandle));
+ }
+ }
+ }
+ return profileBadges;
+ }
+
+ private Drawable getProfileBadge(UserHandle userHandle) {
+ if (SdkLevel.isAtLeastV()) {
+ Context userContext = mContext.createContextAsUser(userHandle, 0 /* flags */);
+ try {
+ UserManager userManager = userContext.getSystemService(UserManager.class);
+ if (userManager == null) {
+ Log.e(TAG, "Cannot obtain user manager");
+ return null;
+ }
+ return userManager.getUserBadge();
+ } catch (Resources.NotFoundException e) {
+ //Todo(b/318530691): Handle the icon for the profile that is not defined already
+ }
+ }
+ return null;
+ }
+
+ @Override
+ public int getShowInQuietMode(UserId userId) {
+ assertMainThread();
+ if (SdkLevel.isAtLeastV()) {
+ if (mUserManager == null) {
+ Log.e(TAG, "Cannot obtain user manager");
+ return UserProperties.SHOW_IN_QUIET_MODE_DEFAULT;
+ }
+ UserProperties userProperties =
+ mUserManager.getUserProperties(userId.getUserHandle());
+ return userProperties.getShowInQuietMode();
+ }
+ return SHOW_IN_QUIET_MODE_DEFAULT;
+ }
+
+ @Override
+ public List<UserId> getAllUserProfileIds() {
+ assertMainThread();
+ return mUserProfileIds;
+ }
+
+ @Override
+ public boolean isBlockedByAdmin(UserId userId) {
+ assertMainThread();
+ return mIsProfileBlockedByAdminMap.get(userId);
+ }
+ @Override
+ public boolean isProfileOff(UserId userId) {
+ assertMainThread();
+ return mProfileOffStatus.get(userId);
+ }
+
+ private void assertMainThread() {
+ if (Looper.getMainLooper().isCurrentThread()) return;
+
+ throw new IllegalStateException("UserManagerState methods are expected to be called"
+ + "from main thread. " + (Looper.myLooper() == null ? "" : "Current thread "
+ + Looper.myLooper().getThread() + ", Main thread "
+ + Looper.getMainLooper().getThread()));
+ }
+ }
+}
diff --git a/src/com/android/providers/media/photopicker/data/glide/PickerPreloadModelProvider.java b/src/com/android/providers/media/photopicker/data/glide/PickerPreloadModelProvider.java
index 9e5b4dd..c1ff593 100644
--- a/src/com/android/providers/media/photopicker/data/glide/PickerPreloadModelProvider.java
+++ b/src/com/android/providers/media/photopicker/data/glide/PickerPreloadModelProvider.java
@@ -43,12 +43,14 @@
public class PickerPreloadModelProvider implements PreloadModelProvider<GlideLoadable> {
private final Context mContext;
+ private final String mMediaStoreVersion;
private final PreferredColorSpace mPreferredColorSpace;
private final PhotosTabAdapter mAdapter;
public PickerPreloadModelProvider(Context context, PhotosTabAdapter adapter) {
mContext = context;
mAdapter = adapter;
+ mMediaStoreVersion = MediaStore.getVersion(mContext);
final boolean isScreenWideColorGamut =
mContext.getResources().getConfiguration().isScreenWideColorGamut();
@@ -88,13 +90,10 @@
@Override
@Nullable
public RequestBuilder getPreloadRequestBuilder(GlideLoadable loadable) {
- RequestOptions options =
+ final RequestOptions options =
RequestOptions.option(THUMBNAIL_REQUEST, true)
.set(PREFERRED_COLOR_SPACE, mPreferredColorSpace);
- // TODO(b/224725723): Remove media store version from key once MP ids are
- // stable.
- ObjectKey signature =
- loadable.getLoadableSignature(/* prefix= */ MediaStore.getVersion(mContext));
+ final ObjectKey signature = loadable.getLoadableSignature(/* prefix= */ mMediaStoreVersion);
return Glide.with(mContext).asBitmap().apply(options).signature(signature).load(loadable);
}
diff --git a/src/com/android/providers/media/photopicker/data/model/RefreshRequest.java b/src/com/android/providers/media/photopicker/data/model/RefreshRequest.java
new file mode 100644
index 0000000..5b4c3c6
--- /dev/null
+++ b/src/com/android/providers/media/photopicker/data/model/RefreshRequest.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2024 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.providers.media.photopicker.data.model;
+
+public class RefreshRequest {
+ public static RefreshRequest DEFAULT = new RefreshRequest(false, false);
+ private boolean mShouldRefresh;
+ private boolean mShouldInit;
+
+ public RefreshRequest(boolean shouldRefresh, boolean shouldInit) {
+ mShouldRefresh = shouldRefresh;
+ mShouldInit = shouldInit;
+ }
+
+ /**
+ * Returns true if Photo Picker UI should be refreshed, otherwise returns false.
+ */
+ public boolean shouldRefreshPicker() {
+ return mShouldRefresh;
+ }
+
+ /**
+ * Returns true if Photo Picker data might be stale and should be initialized, otherwise
+ * returns false.
+ */
+ public boolean shouldInitPicker() {
+ return mShouldInit;
+ }
+}
diff --git a/src/com/android/providers/media/photopicker/data/model/UserId.java b/src/com/android/providers/media/photopicker/data/model/UserId.java
index 8a2811a..28ae35e 100644
--- a/src/com/android/providers/media/photopicker/data/model/UserId.java
+++ b/src/com/android/providers/media/photopicker/data/model/UserId.java
@@ -119,6 +119,11 @@
}
@Override
+ public int hashCode() {
+ return Integer.hashCode(mUserHandle.getIdentifier());
+ }
+
+ @Override
public String toString() {
return String.valueOf(this.mUserHandle.getIdentifier());
}
diff --git a/src/com/android/providers/media/photopicker/sync/EndlessWorker.java b/src/com/android/providers/media/photopicker/sync/EndlessWorker.java
new file mode 100644
index 0000000..6074c88
--- /dev/null
+++ b/src/com/android/providers/media/photopicker/sync/EndlessWorker.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) 2023 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.providers.media.photopicker.sync;
+
+import android.content.Context;
+import android.util.Log;
+
+import androidx.annotation.NonNull;
+import androidx.work.ListenableWorker;
+import androidx.work.OneTimeWorkRequest;
+import androidx.work.WorkManager;
+import androidx.work.Worker;
+import androidx.work.WorkerParameters;
+
+import java.util.concurrent.TimeUnit;
+
+/**
+ * A worker that just enqueues itself endlessly to prevent WorkManager's RescheduleReceiver from
+ * becoming disabled. The work must continue!
+ */
+public class EndlessWorker extends Worker {
+
+ private static final String TAG = "EndlessWorker";
+
+ public EndlessWorker(@NonNull Context context, @NonNull WorkerParameters workerParameters) {
+ super(context, workerParameters);
+ }
+
+ @Override
+ public ListenableWorker.Result doWork() {
+
+ // Immediately enqueue another worker to continue the endless loop.
+ OneTimeWorkRequest request =
+ new OneTimeWorkRequest.Builder(EndlessWorker.class)
+ .setInitialDelay(365, TimeUnit.DAYS)
+ .build();
+ WorkManager.getInstance().enqueue(request);
+ Log.i(TAG, "successfully enqueued the next worker.");
+
+ return ListenableWorker.Result.success();
+ }
+}
diff --git a/src/com/android/providers/media/photopicker/sync/PickerSyncManager.java b/src/com/android/providers/media/photopicker/sync/PickerSyncManager.java
index b086967..5bfb25d 100644
--- a/src/com/android/providers/media/photopicker/sync/PickerSyncManager.java
+++ b/src/com/android/providers/media/photopicker/sync/PickerSyncManager.java
@@ -42,7 +42,6 @@
import com.android.modules.utils.BackgroundThread;
import com.android.providers.media.ConfigStore;
-import com.android.providers.media.photopicker.PickerSyncController;
import com.google.common.util.concurrent.ListenableFuture;
@@ -92,13 +91,14 @@
private static final int SYNC_MEDIA_PERIODIC_WORK_INTERVAL = 4; // Time unit is hours.
private static final int RESET_ALBUM_MEDIA_PERIODIC_WORK_INTERVAL = 12; // Time unit is hours.
- private static final String PERIODIC_SYNC_WORK_NAME;
+ public static final String PERIODIC_SYNC_WORK_NAME;
private static final String PROACTIVE_LOCAL_SYNC_WORK_NAME;
private static final String PROACTIVE_SYNC_WORK_NAME;
public static final String IMMEDIATE_LOCAL_SYNC_WORK_NAME;
private static final String IMMEDIATE_CLOUD_SYNC_WORK_NAME;
public static final String IMMEDIATE_ALBUM_SYNC_WORK_NAME;
- private static final String PERIODIC_ALBUM_RESET_WORK_NAME;
+ public static final String PERIODIC_ALBUM_RESET_WORK_NAME;
+ private static final String ENDLESS_WORK_NAME;
static {
final String syncPeriodicPrefix = "SYNC_MEDIA_PERIODIC_";
@@ -115,6 +115,7 @@
IMMEDIATE_LOCAL_SYNC_WORK_NAME = syncImmediatePrefix + syncLocalSuffix;
IMMEDIATE_CLOUD_SYNC_WORK_NAME = syncImmediatePrefix + syncCloudSuffix;
IMMEDIATE_ALBUM_SYNC_WORK_NAME = "SYNC_ALBUM_MEDIA_IMMEDIATE";
+ ENDLESS_WORK_NAME = "ENDLESS_WORK";
}
private final WorkManager mWorkManager;
@@ -129,6 +130,8 @@
mConfigStore = requireNonNull(configStore);
mContext = requireNonNull(context);
+ setUpEndlessWork();
+
if (shouldSchedulePeriodicSyncs) {
setUpPeriodicWork();
}
@@ -157,6 +160,27 @@
}
/**
+ * Will register a new {@link Worker} for 1 year in the future. This is to prevent the {@link
+ * androidx.work.impl.background.systemalarm.RescheduleReceiver} from being disabled by WM
+ * internals, which triggers PACKAGE_CHANGED broadcasts every time a new worker is scheduled. As
+ * a work around to prevent these broadcasts, we enqueue a worker here very far in the future to
+ * prevent the component from being disabled by work manager.
+ *
+ * <p>{@see b/314863434 for additional context.}
+ */
+ private void setUpEndlessWork() {
+
+ OneTimeWorkRequest request =
+ new OneTimeWorkRequest.Builder(EndlessWorker.class)
+ .setInitialDelay(365, TimeUnit.DAYS)
+ .build();
+
+ mWorkManager.enqueueUniqueWork(
+ ENDLESS_WORK_NAME, ExistingWorkPolicy.KEEP, request);
+ Log.d(TAG, "EndlessWorker has been enqueued");
+ }
+
+ /**
* Returns true if the given unique work is pending. In case the unique work is complete or
* there was an error in getting the work state, it returns false.
*/
@@ -295,10 +319,10 @@
*
* @param albumId is the id of the album that needs to be synced.
* @param authority The authority of the album media.
+ * @param isLocal is {@code true} iff the album authority is of the local provider.
*/
public void syncAlbumMediaForProviderImmediately(
- @NonNull String albumId, @NonNull String authority) {
- boolean isLocal = PickerSyncController.LOCAL_PICKER_PROVIDER_AUTHORITY.equals(authority);
+ @NonNull String albumId, @NonNull String authority, boolean isLocal) {
syncAlbumMediaForProviderImmediately(albumId, getSyncSource(isLocal), authority);
}
diff --git a/src/com/android/providers/media/photopicker/ui/ImageLoader.java b/src/com/android/providers/media/photopicker/ui/ImageLoader.java
index 12433fc..a4a0fac 100644
--- a/src/com/android/providers/media/photopicker/ui/ImageLoader.java
+++ b/src/com/android/providers/media/photopicker/ui/ImageLoader.java
@@ -55,11 +55,13 @@
private static final RequestOptions THUMBNAIL_OPTION =
RequestOptions.option(THUMBNAIL_REQUEST, /* enableThumbnail */ true);
private final Context mContext;
+ private final String mMediaStoreVersion;
private final PreferredColorSpace mPreferredColorSpace;
private static final String PREVIEW_PREFIX = "preview_";
public ImageLoader(Context context) {
mContext = context;
+ mMediaStoreVersion = MediaStore.getVersion(mContext);
final boolean isScreenWideColorGamut =
mContext.getResources().getConfiguration().isScreenWideColorGamut();
@@ -155,11 +157,8 @@
}
private ObjectKey getGlideSignature(GlideLoadable loadable, @Nullable String prefix) {
- // TODO(b/224725723): Remove media store version from key once MP ids are
- // stable.
return loadable.getLoadableSignature(
- /* prefix= */ MediaStore.getVersion(mContext)
- + Optional.ofNullable(prefix).orElse(""));
+ /* prefix= */ mMediaStoreVersion + Optional.ofNullable(prefix).orElse(""));
}
private RequestBuilder<Bitmap> getBitmapRequestBuilder(GlideLoadable loadable) {
diff --git a/src/com/android/providers/media/photopicker/ui/PhotosTabFragment.java b/src/com/android/providers/media/photopicker/ui/PhotosTabFragment.java
index 0917b55..1793914 100644
--- a/src/com/android/providers/media/photopicker/ui/PhotosTabFragment.java
+++ b/src/com/android/providers/media/photopicker/ui/PhotosTabFragment.java
@@ -45,6 +45,7 @@
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
+import com.android.modules.utils.build.SdkLevel;
import com.android.providers.media.R;
import com.android.providers.media.photopicker.data.PaginationParameters;
import com.android.providers.media.photopicker.data.glide.PickerPreloadModelProvider;
@@ -356,20 +357,25 @@
super.onResume();
final String title;
final LayoutModeUtils.Mode layoutMode;
- final boolean shouldHideProfileButton;
+ final boolean hideProfileButtonOrProfileMenuButton;
if (mCategory.isDefault()) {
title = "";
layoutMode = MODE_PHOTOS_TAB;
- shouldHideProfileButton = false;
+ hideProfileButtonOrProfileMenuButton = false;
} else {
title = mCategory.getDisplayName(requireContext());
layoutMode = MODE_ALBUM_PHOTOS_TAB;
- shouldHideProfileButton = true;
+ hideProfileButtonOrProfileMenuButton = true;
}
requirePickerActivity().updateCommonLayouts(layoutMode, title);
- hideProfileButton(shouldHideProfileButton);
+ if (mPickerViewModel.getConfigStore().isPrivateSpaceInPhotoPickerEnabled()
+ && SdkLevel.isAtLeastS()) {
+ hideProfileButtonAndProfileMenuButton(hideProfileButtonOrProfileMenuButton);
+ } else {
+ hideProfileButton(hideProfileButtonOrProfileMenuButton);
+ }
if (mIsCloudMediaInPhotoPickerEnabled
&& mCategory == Category.DEFAULT
diff --git a/src/com/android/providers/media/photopicker/ui/ProfileDialogFragment.java b/src/com/android/providers/media/photopicker/ui/ProfileDialogFragment.java
index 8ae8e1a..6e54526 100644
--- a/src/com/android/providers/media/photopicker/ui/ProfileDialogFragment.java
+++ b/src/com/android/providers/media/photopicker/ui/ProfileDialogFragment.java
@@ -41,6 +41,8 @@
import com.android.modules.utils.build.SdkLevel;
import com.android.providers.media.R;
import com.android.providers.media.photopicker.data.UserIdManager;
+import com.android.providers.media.photopicker.data.UserManagerState;
+import com.android.providers.media.photopicker.data.model.UserId;
import com.android.providers.media.photopicker.viewmodel.PickerViewModel;
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
@@ -48,22 +50,58 @@
public class ProfileDialogFragment extends DialogFragment {
private static final String TAG = "ProfileDialog";
+ private UserId mUserIdToSwitch = null;
+
+ public ProfileDialogFragment(UserId userId) {
+ mUserIdToSwitch = userId;
+ }
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
final PickerViewModel pickerViewModel = new ViewModelProvider(requireActivity()).get(
PickerViewModel.class);
- final UserIdManager userIdManager = pickerViewModel.getUserIdManager();
final MaterialAlertDialogBuilder builder = new MaterialAlertDialogBuilder(getActivity());
+ boolean isDialogCreated;
+ if (pickerViewModel.getConfigStore().isPrivateSpaceInPhotoPickerEnabled()
+ && SdkLevel.isAtLeastS()) {
+ isDialogCreated = createDialogForMultiProfile(pickerViewModel, builder);
+ } else {
+ isDialogCreated = createDialogForWorkProfile(pickerViewModel, builder);
+ }
+
+ if (!isDialogCreated) {
+ return null;
+ }
+ return builder.create();
+ }
+
+ private boolean createDialogForWorkProfile(PickerViewModel pickerViewModel,
+ MaterialAlertDialogBuilder builder) {
+ final UserIdManager userIdManager = pickerViewModel.getUserIdManager();
if (userIdManager.isBlockedByAdmin()) {
setBlockedByAdminParams(userIdManager.isManagedUserSelected(), builder);
} else if (userIdManager.isWorkProfileOff()) {
setWorkProfileOffParams(builder);
} else {
Log.e(TAG, "Unknown error for profile dialog");
- return null;
+ return false;
}
- return builder.create();
+ return true;
+ }
+
+ @RequiresApi(Build.VERSION_CODES.S)
+ private boolean createDialogForMultiProfile(PickerViewModel pickerViewModel,
+ MaterialAlertDialogBuilder builder) {
+ final UserManagerState userManagerState = pickerViewModel.getUserManagerState();
+ if (userManagerState.isBlockedByAdmin(mUserIdToSwitch)) {
+ setBlockedByAdminParams(userManagerState, builder);
+ } else if (userManagerState.isProfileOff(mUserIdToSwitch)) {
+ setProfileOffParams(builder, userManagerState);
+ } else {
+ Log.e(TAG, "Unknown error for profile dialog");
+ return false;
+ }
+ return true;
}
private void setBlockedByAdminParams(
@@ -75,7 +113,7 @@
BLOCKED_BY_ADMIN_TITLE, R.string.picker_profile_admin_title);
message = isManagedUserSelected
? getUpdatedEnterpriseString(
- BLOCKED_FROM_WORK_MESSAGE, R.string.picker_profile_admin_msg_from_work)
+ BLOCKED_FROM_WORK_MESSAGE, R.string.picker_profile_admin_msg_from_work)
: getUpdatedEnterpriseString(
BLOCKED_FROM_PERSONAL_MESSAGE,
R.string.picker_profile_admin_msg_from_personal);
@@ -85,10 +123,44 @@
? getString(R.string.picker_profile_admin_msg_from_work)
: getString(R.string.picker_profile_admin_msg_from_personal);
}
- builder.setIcon(R.drawable.ic_lock);
- builder.setTitle(title);
- builder.setMessage(message);
- builder.setPositiveButton(android.R.string.ok, null);
+
+ setDialogParams(builder, null, title, message);
+ }
+
+ @RequiresApi(Build.VERSION_CODES.S)
+ private void setBlockedByAdminParams(UserManagerState userManagerState,
+ MaterialAlertDialogBuilder builder) {
+ String title;
+ String message;
+ boolean isManagedUserSelected =
+ userManagerState.isManagedUserProfile(userManagerState.getCurrentUserProfileId());
+ if (SdkLevel.isAtLeastV()) {
+ String currentUserProfileLabel =
+ userManagerState.getProfileLabelsForAll().get(UserId.CURRENT_USER);
+ String switchUserProfileLabel =
+ userManagerState.getProfileLabelsForAll().get(mUserIdToSwitch);
+
+ title = getString(R.string.picker_profile_admin_title);
+ message = getString(R.string.picker_profile_admin_msg,
+ switchUserProfileLabel, currentUserProfileLabel);
+
+ } else if (SdkLevel.isAtLeastT()) {
+ title = getUpdatedEnterpriseString(
+ BLOCKED_BY_ADMIN_TITLE, R.string.picker_profile_admin_title);
+ message = isManagedUserSelected
+ ? getUpdatedEnterpriseString(
+ BLOCKED_FROM_WORK_MESSAGE, R.string.picker_profile_admin_msg_from_work)
+ : getUpdatedEnterpriseString(
+ BLOCKED_FROM_PERSONAL_MESSAGE,
+ R.string.picker_profile_admin_msg_from_personal);
+ } else {
+ title = getString(R.string.picker_profile_admin_title);
+ message = isManagedUserSelected
+ ? getString(R.string.picker_profile_admin_msg_from_work)
+ : getString(R.string.picker_profile_admin_msg_from_personal);
+ }
+
+ setDialogParams(builder, null, title, message);
}
private void setWorkProfileOffParams(MaterialAlertDialogBuilder builder) {
@@ -106,12 +178,54 @@
title = getContext().getString(R.string.picker_profile_work_paused_title);
message = getContext().getString(R.string.picker_profile_work_paused_msg);
}
- builder.setIcon(icon);
- builder.setTitle(title);
- builder.setMessage(message);
// TODO(b/197199728): Add listener to turn on apps. This maybe a bit tricky because
// after turning on Work profile, work profile MediaProvider may not be available
// immediately.
+ setDialogParams(builder, icon, title, message);
+ }
+ private void setProfileOffParams(
+ MaterialAlertDialogBuilder builder, UserManagerState userManagerState) {
+
+ Drawable icon;
+ String title;
+ String message;
+
+ if (SdkLevel.isAtLeastV()) {
+ String switchUserProfileLabel =
+ userManagerState.getProfileLabelsForAll().get(mUserIdToSwitch);
+
+ icon = userManagerState.getProfileBadgeForAll().get(mUserIdToSwitch);
+ title = getContext().getString(
+ R.string.picker_profile_paused_title, switchUserProfileLabel);
+ message = getContext().getString(R.string.picker_profile_paused_msg,
+ switchUserProfileLabel, switchUserProfileLabel);
+ } else if (SdkLevel.isAtLeastT()) {
+ icon = getUpdatedWorkProfileIcon();
+ title = getUpdatedEnterpriseString(
+ WORK_PROFILE_PAUSED_TITLE, R.string.picker_profile_work_paused_title);
+ message = getUpdatedEnterpriseString(
+ WORK_PROFILE_PAUSED_MESSAGE, R.string.picker_profile_work_paused_msg);
+ } else {
+ icon = getContext().getDrawable(R.drawable.ic_work_outline);
+ title = getContext().getString(R.string.picker_profile_work_paused_title);
+ message = getContext().getString(R.string.picker_profile_work_paused_msg);
+ }
+
+ // TODO(b/197199728): Add listener to turn on apps. This maybe a bit tricky because
+ // after turning on Work profile, work profile MediaProvider may not be available
+ // immediately.
+ setDialogParams(builder, icon, title, message);
+ }
+
+ private void setDialogParams(MaterialAlertDialogBuilder builder, Drawable icon, String title,
+ String message) {
+ if (icon == null) {
+ builder.setIcon(R.drawable.ic_lock);
+ } else {
+ builder.setIcon(icon);
+ }
+ builder.setTitle(title);
+ builder.setMessage(message);
builder.setPositiveButton(android.R.string.ok, null);
}
@@ -128,10 +242,13 @@
getContext().getDrawable(R.drawable.ic_work_outline));
}
- public static void show(FragmentManager fm) {
+ /**
+ * Show a profile dialog when given user profile doesn't allow cross profile interaction.
+ */
+ public static void show(FragmentManager fm, UserId userId) {
FragmentTransaction ft = fm.beginTransaction();
- Fragment f = new ProfileDialogFragment();
+ Fragment f = new ProfileDialogFragment(userId);
ft.add(f, TAG);
ft.commitAllowingStateLoss();
}
-}
+}
\ No newline at end of file
diff --git a/src/com/android/providers/media/photopicker/ui/TabFragment.java b/src/com/android/providers/media/photopicker/ui/TabFragment.java
index 14159c2..326930b 100644
--- a/src/com/android/providers/media/photopicker/ui/TabFragment.java
+++ b/src/com/android/providers/media/photopicker/ui/TabFragment.java
@@ -25,6 +25,7 @@
import android.app.admin.DevicePolicyManager;
import android.content.Context;
import android.content.Intent;
+import android.content.pm.UserProperties;
import android.content.res.ColorStateList;
import android.content.res.TypedArray;
import android.graphics.drawable.Drawable;
@@ -32,6 +33,7 @@
import android.os.Bundle;
import android.text.TextUtils;
import android.util.Log;
+import android.util.Pair;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
@@ -39,12 +41,16 @@
import android.view.animation.Animation;
import android.view.animation.AnimationUtils;
import android.widget.Button;
+import android.widget.LinearLayout;
+import android.widget.PopupWindow;
import android.widget.TextView;
import androidx.annotation.ColorInt;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi;
+import androidx.core.content.ContextCompat;
+import androidx.core.graphics.drawable.DrawableCompat;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentActivity;
import androidx.lifecycle.LiveData;
@@ -54,16 +60,20 @@
import androidx.recyclerview.widget.RecyclerView;
import com.android.modules.utils.build.SdkLevel;
+import com.android.providers.media.ConfigStore;
import com.android.providers.media.R;
import com.android.providers.media.photopicker.PhotoPickerActivity;
import com.android.providers.media.photopicker.data.Selection;
import com.android.providers.media.photopicker.data.UserIdManager;
+import com.android.providers.media.photopicker.data.UserManagerState;
+import com.android.providers.media.photopicker.data.model.UserId;
import com.android.providers.media.photopicker.viewmodel.PickerViewModel;
import com.google.android.material.floatingactionbutton.ExtendedFloatingActionButton;
import java.text.NumberFormat;
import java.util.Locale;
+import java.util.Map;
/**
* The base abstract Tab fragment
@@ -76,8 +86,10 @@
protected AutoFitRecyclerView mRecyclerView;
private ExtendedFloatingActionButton mProfileButton;
+ private ExtendedFloatingActionButton mProfileMenuButton;
private UserIdManager mUserIdManager;
- private boolean mHideProfileButton;
+ private UserManagerState mUserManagerState;
+ private boolean mHideProfileButtonAndProfileMenuButton;
private View mEmptyView;
private TextView mEmptyTextView;
private boolean mIsAccessibilityEnabled;
@@ -93,6 +105,8 @@
private int mButtonIconAndTextColor;
@ColorInt
+ private int mProfileMenuButtonIconAndTextColor;
+ @ColorInt
private int mButtonBackgroundColor;
@ColorInt
@@ -102,11 +116,33 @@
private int mButtonDisabledBackgroundColor;
private int mRecyclerViewBottomPadding;
+ private boolean mIsProfileButtonVisible = false;
+ private boolean mIsProfileMenuButtonVisible = false;
+ private static PopupWindow sProfileMenuWindow = null;
private RecyclerView.OnScrollListener mOnScrollListenerForMultiProfileButton;
private final MutableLiveData<Boolean> mIsBottomBarVisible = new MutableLiveData<>(false);
- private final MutableLiveData<Boolean> mIsProfileButtonVisible = new MutableLiveData<>(false);
+ private final MutableLiveData<Boolean> mIsProfileButtonOrProfileMenuButtonVisible =
+ new MutableLiveData<>(false);
+ private ConfigStore mConfigStore;
+
+ /**
+ * In case of multiuser profile, it represents the number of profiles that are off
+ * (In quiet mode) with {@link UserProperties.SHOW_IN_QUIET_MODE_HIDDEN}. Such profiles
+ * in quiet mode will not appear in photopicker.
+ */
+ private int mHideProfileCount = 0;
+
+ /**
+ * This member variable is relevant to get the userId (other than current user) when only two
+ * number of profiles those either unlocked/on or don't have
+ * {@link UserProperties.SHOW_IN_QUIET_MODE_HIDDEN}, are available on the device.
+ * we are using this variable to get label and icon of a userId to update the content
+ * in {@link #mProfileButton}, and at the time when user will press {@link #mProfileButton}
+ * to change the current profile.
+ */
+ private UserId mPotentialUserForProfileButton;
@Override
@NonNull
@@ -128,12 +164,14 @@
mRecyclerView.setHasFixedSize(true);
final ViewModelProvider viewModelProvider = new ViewModelProvider(activity);
mPickerViewModel = viewModelProvider.get(PickerViewModel.class);
+ mConfigStore = mPickerViewModel.getConfigStore();
mSelection = mPickerViewModel.getSelection();
mRecyclerViewBottomPadding = getResources().getDimensionPixelSize(
R.dimen.picker_recycler_view_bottom_padding);
mIsBottomBarVisible.observe(this, val -> updateRecyclerViewBottomPadding());
- mIsProfileButtonVisible.observe(this, val -> updateRecyclerViewBottomPadding());
+ mIsProfileButtonOrProfileMenuButtonVisible.observe(
+ this, val -> updateRecyclerViewBottomPadding());
mEmptyView = view.findViewById(android.R.id.empty);
mEmptyTextView = mEmptyView.findViewById(R.id.empty_text_view);
@@ -147,13 +185,17 @@
taDisabled.recycle();
final int[] attrs =
- new int[]{R.attr.pickerProfileButtonColor, R.attr.pickerProfileButtonTextColor};
+ new int[]{R.attr.pickerProfileButtonColor, R.attr.pickerProfileButtonTextColor,
+ android.R.attr.textColorPrimary};
final TypedArray ta = context.obtainStyledAttributes(attrs);
mButtonBackgroundColor = ta.getColor(/* index */ 0, /* defValue */ -1);
mButtonIconAndTextColor = ta.getColor(/* index */ 1, /* defValue */ -1);
+ mProfileMenuButtonIconAndTextColor = ta.getColor(/* index */ 2, /* defValue */ -1);
ta.recycle();
mProfileButton = activity.findViewById(R.id.profile_button);
+ mProfileMenuButton = activity.findViewById(R.id.profile_menu_button);
+ mUserManagerState = mPickerViewModel.getUserManagerState();
mUserIdManager = mPickerViewModel.getUserIdManager();
final boolean canSelectMultiple = mSelection.canSelectMultiple();
@@ -196,7 +238,12 @@
// Fetch activity or context again instead of capturing existing variable in lambdas
// to avoid memory leaks.
try {
- updateProfileButtonVisibility();
+ if (mConfigStore.isPrivateSpaceInPhotoPickerEnabled()
+ && SdkLevel.isAtLeastS()) {
+ updateProfileButtonAndProfileMenuButtonVisibility();
+ } else {
+ updateProfileButtonVisibility();
+ }
updateVisibilityAndAnimateBottomBar(requireContext(), selectedItemListSize);
} catch (RuntimeException e) {
Log.e(TAG, "Fragment is likely not attached to an activity. ", e);
@@ -204,12 +251,44 @@
});
}
+ if (mConfigStore.isPrivateSpaceInPhotoPickerEnabled() && SdkLevel.isAtLeastS()) {
+ setUpObserverForCrossProfileAndMultiUserChangeGeneric();
+
+ // Initial setup
+ setUpProfileButtonAndProfileMenuButtonWithListeners(
+ mUserManagerState.isMultiUserProfiles());
+
+ } else {
+ setupObserverForCrossProfileAccess();
+
+ // Initial setup
+ setUpProfileButtonWithListeners(mUserIdManager.isMultiUserProfiles());
+ }
+
+
+ final AccessibilityManager accessibilityManager =
+ context.getSystemService(AccessibilityManager.class);
+ mIsAccessibilityEnabled = accessibilityManager.isEnabled();
+ accessibilityManager.addAccessibilityStateChangeListener(enabled -> {
+ mIsAccessibilityEnabled = enabled;
+ if (mConfigStore.isPrivateSpaceInPhotoPickerEnabled() && SdkLevel.isAtLeastS()) {
+ setUpProfileButtonAndProfileMenuButtonWithListeners(
+ mUserManagerState.isMultiUserProfiles());
+ } else {
+ setUpProfileButtonWithListeners(mUserIdManager.isMultiUserProfiles());
+ }
+ });
+
+ }
+
+ private void setupObserverForCrossProfileAccess() {
// Observe for cross profile access changes.
final LiveData<Boolean> crossProfileAllowed = mUserIdManager.getCrossProfileAllowed();
if (crossProfileAllowed != null) {
crossProfileAllowed.observe(this, isCrossProfileAllowed -> {
setUpProfileButton();
- if (Boolean.TRUE.equals(mIsProfileButtonVisible.getValue())) {
+ if (Boolean.TRUE.equals(
+ mIsProfileButtonOrProfileMenuButtonVisible.getValue())) {
if (isCrossProfileAllowed) {
mPickerViewModel.logProfileSwitchButtonEnabled();
} else {
@@ -219,28 +298,72 @@
});
}
-
- final AccessibilityManager accessibilityManager =
- context.getSystemService(AccessibilityManager.class);
- mIsAccessibilityEnabled = accessibilityManager.isEnabled();
- accessibilityManager.addAccessibilityStateChangeListener(enabled -> {
- mIsAccessibilityEnabled = enabled;
- setUpProfileButtonWithListeners(mUserIdManager.isMultiUserProfiles());
- });
-
// Observe for multi-user changes.
final LiveData<Boolean> isMultiUserProfiles = mUserIdManager.getIsMultiUserProfiles();
if (isMultiUserProfiles != null) {
isMultiUserProfiles.observe(this, this::setUpProfileButtonWithListeners);
}
-
- // Initial setup
- setUpProfileButtonWithListeners(mUserIdManager.isMultiUserProfiles());
}
+ @RequiresApi(Build.VERSION_CODES.S)
+ private void setUpObserverForCrossProfileAndMultiUserChangeGeneric() {
+ // Observe for cross profile access changes.
+ final LiveData<Map<UserId, Boolean>> crossProfileAllowed =
+ mUserManagerState.getCrossProfileAllowed();
+ if (crossProfileAllowed != null) {
+ crossProfileAllowed.observe(this , crossProfileAllowedStatus -> {
+ setUpProfileButtonAndProfileMenuButton();
+ // Todo(b/318339948): need to put log metrics like present above;
+ });
+ }
+
+ // Observe for multi-user changes.
+ final LiveData<Boolean> isMultiUserProfiles =
+ mUserManagerState.getIsMultiUserProfiles();
+ if (isMultiUserProfiles != null) {
+ isMultiUserProfiles.observe(this, isMultiUserProfilesAvailable -> {
+ setUpProfileButtonAndProfileMenuButtonWithListeners(isMultiUserProfilesAvailable);
+ });
+ }
+ }
+
+ @RequiresApi(Build.VERSION_CODES.S)
+ private void updateUserForProfileButtonAndHideProfileCount() {
+ mHideProfileCount = 0;
+ mPotentialUserForProfileButton = null;
+ for (UserId userId : mUserManagerState.getAllUserProfileIds()) {
+ if (isProfileHideInQuietMode(userId)) {
+ mHideProfileCount += 1;
+ } else if (!userId.equals(UserId.CURRENT_USER)) {
+ mPotentialUserForProfileButton = userId;
+ }
+ }
+
+ // we will use {@link #mPotentialUserForProfileButton} only to show profile button and
+ // profile button will only be visible when two profiles are available on the device
+ if (mUserManagerState.getProfileCount() - mHideProfileCount != 2) {
+ mPotentialUserForProfileButton = null;
+ }
+ }
+
+ private boolean isProfileHideInQuietMode(UserId userId) {
+ if (!SdkLevel.isAtLeastV()) {
+ return false;
+ }
+ /*
+ * Any profile with {@link UserProperties.SHOW_IN_QUIET_MODE_HIDDEN} will not appear in
+ * quiet mode in Photopicker.
+ */
+ return mUserManagerState.isProfileOff(userId)
+ && mUserManagerState.getShowInQuietMode(userId)
+ == UserProperties.SHOW_IN_QUIET_MODE_HIDDEN;
+ }
+
+
private void updateRecyclerViewBottomPadding() {
final int recyclerViewBottomPadding;
- if (mIsProfileButtonVisible.getValue() || mIsBottomBarVisible.getValue()) {
+ if (mIsProfileButtonOrProfileMenuButtonVisible.getValue()
+ || mIsBottomBarVisible.getValue()) {
recyclerViewBottomPadding = mRecyclerViewBottomPadding;
} else {
recyclerViewBottomPadding = 0;
@@ -312,6 +435,131 @@
mRecyclerView.addOnScrollListener(mOnScrollListenerForMultiProfileButton);
}
+ @RequiresApi(Build.VERSION_CODES.S)
+ private void setUpListenersForProfileButtonAndProfileMenuButton() {
+ mProfileButton.setOnClickListener(v -> onClickProfileButtonGeneric());
+ mProfileMenuButton.setOnClickListener(v -> onClickProfileMenuButton(v));
+ mOnScrollListenerForMultiProfileButton = new RecyclerView.OnScrollListener() {
+ @Override
+ public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
+ super.onScrolled(recyclerView, dx, dy);
+
+ // Do not change profile button visibility on scroll if Accessibility mode is
+ // enabled. This is done to enhance button visibility in Accessibility mode.
+ if (mIsAccessibilityEnabled) {
+ return;
+ }
+
+ if (dy > 0) {
+ mProfileButton.hide();
+ mProfileMenuButton.hide();
+ } else {
+ updateProfileButtonAndProfileMenuButtonVisibility();
+ }
+ }
+ };
+ mRecyclerView.addOnScrollListener(mOnScrollListenerForMultiProfileButton);
+ }
+
+ @RequiresApi(Build.VERSION_CODES.S)
+ private void onClickProfileMenuButton(View view) {
+ initialiseProfileMenuWindow();
+ View profileMenuView = LayoutInflater.from(requireContext()).inflate(
+ R.layout.profile_menu_layout, null);
+ sProfileMenuWindow.setContentView(profileMenuView);
+ LinearLayout profileMenuContainer = profileMenuView.findViewById(
+ R.id.profile_menu_container);
+
+ Map<UserId, Drawable> profileBadges = mUserManagerState.getProfileBadgeForAll();
+ Map<UserId, String> profileLabels = mUserManagerState.getProfileLabelsForAll();
+
+ // Add profile menu items to profile menu.
+ for (UserId userId : mUserManagerState.getAllUserProfileIds()) {
+ if (!isProfileHideInQuietMode(userId)) {
+ View profileMenuItemView = LayoutInflater.from(requireContext()).inflate(
+ R.layout.profile_menu_item, profileMenuContainer, false);
+
+ // Set label and icon in profile menu item
+ TextView profileMenuItem = profileMenuItemView.findViewById(R.id.profile_label);
+ String label = profileLabels.get(userId);
+ Drawable icon = profileBadges.get(userId);
+ boolean isSwitchingAllowed = canSwitchToUser(userId);
+ final int textAndIconColor = isSwitchingAllowed
+ ? mProfileMenuButtonIconAndTextColor : mButtonDisabledIconAndTextColor;
+ DrawableCompat.setTintList(icon, ColorStateList.valueOf(textAndIconColor));
+ profileMenuItem.setTextColor(ColorStateList.valueOf(textAndIconColor));
+ profileMenuItem.setText(label);
+ profileMenuItem.setCompoundDrawablesWithIntrinsicBounds(
+ icon, null, null, null);
+ // Set padding between icon anf label in profile menu button
+ int paddingDp = getResources().getDimensionPixelSize(
+ R.dimen.popup_window_title_icon_padding);
+ int paddingPixels = (int) (paddingDp * getResources().getDisplayMetrics().density);
+ profileMenuItem.setCompoundDrawablePadding(paddingPixels);
+
+ // Add click listener
+ profileMenuItemView.setOnClickListener(v -> onClickProfileMenuItem(userId));
+ profileMenuContainer.addView(profileMenuItemView);
+ }
+ }
+
+ /*
+ * we need estimated dimensions of {@link #sProfileMenuWindow} to open dropdown just above
+ * the {@link #mProfileMenuButton}
+ */
+ sProfileMenuWindow.showAsDropDown(
+ view, view.getWidth() / 2 - getProfileMenuWindowDimensions().first / 2,
+ -(getProfileMenuWindowDimensions().second + view.getHeight()));
+ }
+
+ private void initialiseProfileMenuWindow() {
+ if (sProfileMenuWindow != null) {
+ sProfileMenuWindow.dismiss();
+ }
+ sProfileMenuWindow = new PopupWindow(requireContext());
+ sProfileMenuWindow.setWidth(ViewGroup.LayoutParams.WRAP_CONTENT);
+ sProfileMenuWindow.setHeight(ViewGroup.LayoutParams.WRAP_CONTENT);
+ sProfileMenuWindow.setFocusable(true);
+ sProfileMenuWindow.setBackgroundDrawable(
+ ContextCompat.getDrawable(requireContext(), R.drawable.profile_menu_background));
+ }
+
+ @RequiresApi(Build.VERSION_CODES.S)
+ private boolean canSwitchToUser(UserId userId) {
+ return mUserManagerState.getCrossProfileAllowedStatusForAll().get(userId);
+ }
+
+ @RequiresApi(Build.VERSION_CODES.S)
+ private void onClickProfileMenuItem(UserId userId) {
+ // Check if current user profileId is not same as given userId, where user want to switch
+ if (!userId.equals(mUserManagerState.getCurrentUserProfileId())) {
+ if (canSwitchToUser(userId)) {
+ changeProfileGeneric(userId);
+ } else {
+ try {
+ ProfileDialogFragment.show(
+ requireActivity().getSupportFragmentManager(), userId);
+ } catch (RuntimeException e) {
+ Log.e(TAG, "Fragment is likely not attached to an activity. ", e);
+ }
+ }
+ }
+ sProfileMenuWindow.dismiss();
+ }
+
+ /**
+ * To get estimated dimensions of {@link #sProfileMenuWindow};
+ * @return a pair of two Integers, first represents width and second represents height
+ */
+ private Pair<Integer, Integer> getProfileMenuWindowDimensions() {
+ int width = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED);
+ int height = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED);
+ sProfileMenuWindow.getContentView().measure(width, height);
+
+ return new Pair<>(sProfileMenuWindow.getContentView().getMeasuredWidth(),
+ sProfileMenuWindow.getContentView().getMeasuredHeight());
+ }
+
@Override
public void onDestroy() {
super.onDestroy();
@@ -330,6 +578,18 @@
setUpProfileButton();
}
+ @RequiresApi(Build.VERSION_CODES.S)
+ private void setUpProfileButtonAndProfileMenuButtonWithListeners(boolean isMultiUserProfile) {
+ if (mOnScrollListenerForMultiProfileButton != null) {
+ mRecyclerView.removeOnScrollListener(mOnScrollListenerForMultiProfileButton);
+ }
+ if (isMultiUserProfile) {
+ setUpListenersForProfileButtonAndProfileMenuButton();
+ }
+ setUpProfileButtonAndProfileMenuButton();
+
+ }
+
private void setUpProfileButton() {
updateProfileButtonVisibility();
if (!mUserIdManager.isMultiUserProfiles()) {
@@ -340,20 +600,51 @@
updateProfileButtonColor(/* isDisabled */ !mUserIdManager.isCrossProfileAllowed());
}
+ @RequiresApi(Build.VERSION_CODES.S)
+ private void setUpProfileButtonAndProfileMenuButton() {
+ // Dismiss profile menu if user remove/lock any profile in the background while profile
+ // menu window was opened.
+ if (sProfileMenuWindow != null) {
+ sProfileMenuWindow.dismiss();
+ }
+ updateUserForProfileButtonAndHideProfileCount();
+ updateProfileButtonAndProfileMenuButtonVisibility();
+ if (!mUserManagerState.isMultiUserProfiles()) {
+ return;
+ }
+
+
+ updateProfileButtonAndProfileMenuButtonContent();
+ updateProfileButtonAndProfileMenuButtonColor();
+ }
+
+
+
private boolean shouldShowProfileButton() {
return mUserIdManager.isMultiUserProfiles()
- && !mHideProfileButton
+ && !mHideProfileButtonAndProfileMenuButton
&& !mPickerViewModel.isUserSelectForApp()
&& (!mSelection.canSelectMultiple()
|| mSelection.getSelectedItemCount().getValue() == 0);
}
+ @RequiresApi(Build.VERSION_CODES.S)
+ private boolean shouldShowProfileButtonOrProfileMenuButton() {
+ return mUserManagerState.isMultiUserProfiles()
+ && (mUserManagerState.getProfileCount() - mHideProfileCount) > 1
+ && !mHideProfileButtonAndProfileMenuButton
+ && !mPickerViewModel.isUserSelectForApp()
+ && (!mSelection.canSelectMultiple()
+ || mSelection.getSelectedItemCount().getValue() == 0);
+ }
+
private void onClickProfileButton() {
mPickerViewModel.logProfileSwitchButtonClick();
if (!mUserIdManager.isCrossProfileAllowed()) {
try {
- ProfileDialogFragment.show(requireActivity().getSupportFragmentManager());
+ ProfileDialogFragment.show(requireActivity().getSupportFragmentManager(),
+ (UserId) null);
} catch (RuntimeException e) {
Log.e(TAG, "Fragment is likely not attached to an activity. ", e);
}
@@ -362,6 +653,44 @@
}
}
+
+ /**
+ * This method is relevant to get the userId (other than current user profile) when only two
+ * number of profiles those either unlocked/on or don't have
+ * {@link UserProperties.SHOW_IN_QUIET_MODE_HIDDEN}, are available on the device.
+ * we are using this method to get label and icon of a userId to update the content
+ * in {@link #mProfileButton}, and at the time when user will press {@link #mProfileButton}
+ * to change the current profile.
+ */
+ @RequiresApi(Build.VERSION_CODES.S)
+ private UserId getUserToSwitchFromProfileButton() {
+ if (mPotentialUserForProfileButton != null
+ && mPotentialUserForProfileButton.equals(
+ mUserManagerState.getCurrentUserProfileId())) {
+ return UserId.CURRENT_USER;
+ }
+ return mPotentialUserForProfileButton;
+ }
+
+ @RequiresApi(Build.VERSION_CODES.S)
+ private void onClickProfileButtonGeneric() {
+ // todo add logs like above
+
+ UserId userIdToSwitch = getUserToSwitchFromProfileButton();
+ if (userIdToSwitch != null) {
+ if (canSwitchToUser(userIdToSwitch)) {
+ changeProfileGeneric(userIdToSwitch);
+ } else {
+ try {
+ ProfileDialogFragment.show(
+ requireActivity().getSupportFragmentManager(), userIdToSwitch);
+ } catch (RuntimeException e) {
+ Log.e(TAG, "Fragment is likely not attached to an activity. ", e);
+ }
+ }
+ }
+ }
+
private void changeProfile() {
if (mUserIdManager.isManagedUserSelected()) {
// TODO(b/190024747): Add caching for performance before switching data to and fro
@@ -379,6 +708,14 @@
mPickerViewModel.onSwitchedProfile();
}
+ @RequiresApi(Build.VERSION_CODES.S)
+ private void changeProfileGeneric(UserId userIdSwitchTo) {
+ mUserManagerState.setUserAsCurrentUserProfile(userIdSwitchTo);
+ updateProfileButtonAndProfileMenuButtonContent();
+
+ mPickerViewModel.onSwitchedProfile();
+ }
+
private void updateProfileButtonContent(boolean isManagedUserSelected) {
final Drawable icon;
final String text;
@@ -402,6 +739,53 @@
mProfileButton.setText(text);
}
+ @RequiresApi(Build.VERSION_CODES.S)
+ private void updateProfileButtonAndProfileMenuButtonContent() {
+ final Context context;
+ final Drawable profileButtonIcon, profileMenuButtonIcon;
+ final String profileButtonText, profileMenuButtonText;
+ final UserId currentUserProfileId = mUserManagerState.getCurrentUserProfileId();
+ try {
+ context = requireContext();
+ } catch (RuntimeException e) {
+ Log.e(TAG, "Could not update profile button content because the fragment is not"
+ + " attached.");
+ return;
+ }
+
+ if (mIsProfileMenuButtonVisible) {
+ profileMenuButtonIcon =
+ mUserManagerState.getProfileBadgeForAll().get(currentUserProfileId);
+ profileMenuButtonText =
+ mUserManagerState.getProfileLabelsForAll().get(currentUserProfileId);
+ mProfileMenuButton.setIcon(profileMenuButtonIcon);
+ mProfileMenuButton.setText(profileMenuButtonText);
+ }
+
+ if (mIsProfileButtonVisible) {
+ UserId userIdToSwitch = getUserToSwitchFromProfileButton();
+ if (userIdToSwitch != null) {
+ if (SdkLevel.isAtLeastV()) {
+ profileButtonIcon =
+ mUserManagerState.getProfileBadgeForAll().get(userIdToSwitch);
+ profileButtonText = context.getString(R.string.picker_profile_switch_message,
+ mUserManagerState.getProfileLabelsForAll().get(userIdToSwitch));
+ } else {
+ if (mUserManagerState.isManagedUserProfile(currentUserProfileId)) {
+ profileButtonIcon = context.getDrawable(R.drawable.ic_personal_mode);
+ profileButtonText = getSwitchToPersonalMessage(context);
+ } else {
+ profileButtonIcon = getWorkProfileIcon(context);
+ profileButtonText = getSwitchToWorkMessage(context);
+ }
+ }
+ mProfileButton.setIcon(profileButtonIcon);
+ mProfileButton.setText(profileButtonText);
+ }
+ }
+ }
+
+
private String getSwitchToPersonalMessage(@NonNull Context context) {
if (SdkLevel.isAtLeastT()) {
return getUpdatedEnterpriseString(
@@ -462,19 +846,102 @@
mProfileButton.setBackgroundTintList(ColorStateList.valueOf(backgroundTintColor));
}
+ @RequiresApi(Build.VERSION_CODES.S)
+ private void updateProfileButtonAndProfileMenuButtonColor() {
+ if (mIsProfileButtonVisible) {
+ boolean isDisabled = true;
+ UserId userIdToSwitch = getUserToSwitchFromProfileButton();
+ if (userIdToSwitch != null) {
+ isDisabled = !canSwitchToUser(userIdToSwitch);
+ }
+
+ final int textAndIconColor =
+ isDisabled ? mButtonDisabledIconAndTextColor : mButtonIconAndTextColor;
+ final int backgroundTintColor =
+ isDisabled ? mButtonDisabledBackgroundColor : mButtonBackgroundColor;
+
+ mProfileButton.setTextColor(ColorStateList.valueOf(textAndIconColor));
+ mProfileButton.setIconTint(ColorStateList.valueOf(textAndIconColor));
+ mProfileButton.setBackgroundTintList(ColorStateList.valueOf(backgroundTintColor));
+ }
+
+ if (mIsProfileMenuButtonVisible) {
+ mProfileMenuButton.setIconTint(ColorStateList.valueOf(mButtonIconAndTextColor));
+ }
+ }
+
+
protected void hideProfileButton(boolean hide) {
- mHideProfileButton = hide;
+ mHideProfileButtonAndProfileMenuButton = hide;
updateProfileButtonVisibility();
}
+ @RequiresApi(Build.VERSION_CODES.S)
+ protected void hideProfileButtonAndProfileMenuButton(boolean hide) {
+ mHideProfileButtonAndProfileMenuButton = hide;
+ updateProfileButtonAndProfileMenuButtonVisibility();
+ }
+
+
private void updateProfileButtonVisibility() {
final boolean shouldShowProfileButton = shouldShowProfileButton();
if (shouldShowProfileButton) {
+ mIsProfileButtonVisible = true;
mProfileButton.show();
} else {
+ mIsProfileButtonVisible = false;
mProfileButton.hide();
}
- mIsProfileButtonVisible.setValue(shouldShowProfileButton);
+ mIsProfileButtonOrProfileMenuButtonVisible.setValue(shouldShowProfileButton);
+ }
+
+ @RequiresApi(Build.VERSION_CODES.S)
+ private void updateProfileButtonAndProfileMenuButtonVisibility() {
+ // The button could be either profile button or profile menu button.
+ final boolean shouldShowButton = shouldShowProfileButtonOrProfileMenuButton();
+ setProfileButtonVisibility(shouldShowButton);
+ setProfileMenuButtonVisibility(shouldShowButton);
+ mIsProfileButtonOrProfileMenuButtonVisible.setValue(
+ shouldShowButton);
+ }
+
+ @RequiresApi(Build.VERSION_CODES.S)
+ private void setProfileMenuButtonVisibility(boolean shouldShowProfileMenuButton) {
+ mIsProfileMenuButtonVisible = false;
+ /*
+ * Check if the total number of profiles that will appear separately in PhotoPicker
+ * is less than three. If more than two such profiles are available, we will show a
+ * dropdown menu with {@link #mProfileMenuButton} representing all available visible
+ * profiles instead of showing a {@link #mProfileMenuButton}
+ */
+ if (shouldShowProfileMenuButton
+ && (mUserManagerState.getProfileCount() - mHideProfileCount) >= 3) {
+ mIsProfileMenuButtonVisible = true;
+ mProfileMenuButton.show();
+ }
+
+ if (!mIsProfileMenuButtonVisible) {
+ mProfileMenuButton.hide();
+ }
+ }
+
+ @RequiresApi(Build.VERSION_CODES.S)
+ private void setProfileButtonVisibility(boolean shouldShowProfileButton) {
+ mIsProfileButtonVisible = false;
+ /*
+ * Check if the total number of profiles that will appear separately in PhotoPicker
+ * is less than three. If more than two such profiles are available, we will show a
+ * dropdown menu with {@link #mProfileMenuButton} representing all available visible
+ * profiles instead of showing a {@link #mProfileMenuButton}
+ */
+ if (shouldShowProfileButton
+ && (mUserManagerState.getProfileCount() - mHideProfileCount) < 3) {
+ mIsProfileButtonVisible = true;
+ mProfileButton.show();
+ }
+ if (!mIsProfileButtonVisible) {
+ mProfileButton.hide();
+ }
}
protected void setEmptyMessage(int resId) {
diff --git a/src/com/android/providers/media/photopicker/ui/settings/SettingsProfileSelectFragment.java b/src/com/android/providers/media/photopicker/ui/settings/SettingsProfileSelectFragment.java
index d490684..745069f 100644
--- a/src/com/android/providers/media/photopicker/ui/settings/SettingsProfileSelectFragment.java
+++ b/src/com/android/providers/media/photopicker/ui/settings/SettingsProfileSelectFragment.java
@@ -20,6 +20,7 @@
import android.annotation.NonNull;
import android.annotation.UserIdInt;
+import android.app.ActivityManager;
import android.content.Context;
import android.os.Bundle;
import android.view.View;
@@ -27,7 +28,8 @@
import androidx.fragment.app.Fragment;
import androidx.lifecycle.ViewModelProvider;
-import com.android.providers.media.photopicker.data.UserIdManager;
+import com.android.modules.utils.build.SdkLevel;
+import com.android.providers.media.photopicker.data.model.UserId;
import com.android.settingslib.widget.ProfileSelectFragment;
import com.android.settingslib.widget.R;
@@ -120,15 +122,29 @@
return fragment;
}
+ private int getManagedUser() {
+ if (mSettingsViewModel.getConfigStore().isPrivateSpaceInPhotoPickerEnabled()
+ && SdkLevel.isAtLeastS()) {
+ for (UserId userId : mSettingsViewModel.getUserManagerState().getAllUserProfileIds()) {
+ if (mSettingsViewModel.getUserManagerState().isManagedUserProfile(userId)) {
+ return userId.getIdentifier();
+ }
+ }
+ } else {
+ return mSettingsViewModel.getUserIdManager().getManagedUserId().getIdentifier();
+ }
+ return -1;
+ }
+
@UserIdInt
private int getTabUserId(int tabPosition) {
- final UserIdManager userIdManager = mSettingsViewModel.getUserIdManager();
-
+ int personalUser = ActivityManager.getCurrentUser();
+ int managedUser = getManagedUser();
switch (tabPosition) {
case ProfileSelectFragment.PERSONAL_TAB:
- return userIdManager.getPersonalUserId().getIdentifier();
+ return personalUser;
case ProfileSelectFragment.WORK_TAB:
- return userIdManager.getManagedUserId().getIdentifier();
+ return managedUser;
default:
// tabPosition should match one of the cases above.
throw new IllegalArgumentException("Unidentified tab id " + tabPosition);
diff --git a/src/com/android/providers/media/photopicker/ui/settings/SettingsViewModel.java b/src/com/android/providers/media/photopicker/ui/settings/SettingsViewModel.java
index 34376d0..755ee82 100644
--- a/src/com/android/providers/media/photopicker/ui/settings/SettingsViewModel.java
+++ b/src/com/android/providers/media/photopicker/ui/settings/SettingsViewModel.java
@@ -22,28 +22,51 @@
import androidx.annotation.NonNull;
import androidx.lifecycle.AndroidViewModel;
+import com.android.modules.utils.build.SdkLevel;
+import com.android.providers.media.ConfigStore;
+import com.android.providers.media.MediaApplication;
import com.android.providers.media.photopicker.data.UserIdManager;
+import com.android.providers.media.photopicker.data.UserManagerState;
/**
* SettingsViewModel stores common objects used across PhotoPickerSettingsActivity and
* SettingsProfileSelectFragment. It also stores the tab selected state which helps maintain tab
- * state when activity is destroyed and recrreated.
+ * state when activity is destroyed and recreated.
*/
public class SettingsViewModel extends AndroidViewModel {
public static final int TAB_NOT_SET = -1;
- @NonNull
private final UserIdManager mUserIdManager;
+ private final UserManagerState mUserManagerState;
private int mSelectedTab;
+ private ConfigStore mConfigStore;
public SettingsViewModel(@NonNull Application application) {
super(application);
final Context context = application.getApplicationContext();
- mUserIdManager = UserIdManager.create(context);
+ initConfigStore();
+ if (mConfigStore.isPrivateSpaceInPhotoPickerEnabled() && SdkLevel.isAtLeastS()) {
+ mUserManagerState = UserManagerState.create(context);
+ mUserIdManager = null;
+ } else {
+ mUserIdManager = UserIdManager.create(context);
+ mUserManagerState = null;
+ }
mSelectedTab = TAB_NOT_SET;
}
+ private void initConfigStore() {
+ mConfigStore = MediaApplication.getConfigStore();
+ }
+
+ /**
+ * @return the {@link ConfigStore} for this context.
+ */
+ public ConfigStore getConfigStore() {
+ return mConfigStore;
+ }
+
public void setSelectedTab(int selectedTab) {
mSelectedTab = selectedTab;
}
@@ -52,8 +75,11 @@
return mSelectedTab;
}
- @NonNull
public UserIdManager getUserIdManager() {
return mUserIdManager;
}
+
+ public UserManagerState getUserManagerState() {
+ return mUserManagerState;
+ }
}
diff --git a/src/com/android/providers/media/photopicker/util/CrossProfileUtils.java b/src/com/android/providers/media/photopicker/util/CrossProfileUtils.java
index 301b623..7276361 100644
--- a/src/com/android/providers/media/photopicker/util/CrossProfileUtils.java
+++ b/src/com/android/providers/media/photopicker/util/CrossProfileUtils.java
@@ -21,6 +21,7 @@
import android.content.Intent;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
+import android.os.UserHandle;
import android.os.UserManager;
import android.provider.MediaStore;
import android.util.Log;
@@ -59,6 +60,26 @@
}
/**
+ * Whether given intent is allowed to show cross profile content
+ * for the given user profile. This can be regulated by device admin policies.
+ *
+ * @return {@code true} if the given user profile can access cross profile content.
+ *
+ */
+ public static boolean isIntentAllowedCrossProfileAccessFromUser(Intent intent,
+ PackageManager packageManager, UserHandle userHandle) {
+ intent.setComponent(null);
+ intent.setPackage(null);
+ for (ResolveInfo info : packageManager.queryIntentActivitiesAsUser(
+ intent, PackageManager.MATCH_DEFAULT_ONLY, userHandle)) {
+ if (info != null && info.isCrossProfileIntentForwarderActivity()) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
* Whether {@link MediaProvider} for user profile of {@code userId} is available or not
*
* return {@code false} if managed profile is turned off
diff --git a/src/com/android/providers/media/photopicker/viewmodel/BannerManager.java b/src/com/android/providers/media/photopicker/viewmodel/BannerManager.java
index 7601ee2..5835674 100644
--- a/src/com/android/providers/media/photopicker/viewmodel/BannerManager.java
+++ b/src/com/android/providers/media/photopicker/viewmodel/BannerManager.java
@@ -31,9 +31,11 @@
import androidx.lifecycle.LiveData;
import androidx.lifecycle.MutableLiveData;
+import com.android.modules.utils.build.SdkLevel;
import com.android.providers.media.ConfigStore;
import com.android.providers.media.photopicker.DataLoaderThread;
import com.android.providers.media.photopicker.data.UserIdManager;
+import com.android.providers.media.photopicker.data.UserManagerState;
import com.android.providers.media.photopicker.util.ThreadUtils;
import com.android.providers.media.util.PerUser;
@@ -42,6 +44,7 @@
private static final int DELAY_MILLIS = 0;
private final UserIdManager mUserIdManager;
+ private final UserManagerState mUserManagerState;
// Authority of the current CloudMediaProvider of the current user
private final MutableLiveData<String> mCloudMediaProviderAuthority = new MutableLiveData<>();
@@ -64,9 +67,22 @@
// The banner controllers per user
private final PerUser<BannerController> mBannerControllers;
+ private ConfigStore mConfigStore;
BannerManager(@NonNull Context context, @NonNull UserIdManager userIdManager,
@NonNull ConfigStore configStore) {
+ this(context, userIdManager, null, configStore);
+ }
+
+ BannerManager(@NonNull Context context, @NonNull UserManagerState userManagerState,
+ @NonNull ConfigStore configStore) {
+ this(context, null, userManagerState, configStore);
+ }
+
+ private BannerManager(@NonNull Context context, UserIdManager userIdManager,
+ UserManagerState userManagerState, ConfigStore configStore) {
+ mConfigStore = configStore;
+ mUserManagerState = userManagerState;
mUserIdManager = userIdManager;
mBannerControllers = new PerUser<BannerController>() {
@NonNull
@@ -82,10 +98,14 @@
@NonNull
BannerController createBannerController(@NonNull Context context,
@NonNull UserHandle userHandle, @NonNull ConfigStore configStore) {
+ mConfigStore = configStore;
return new BannerController(context, userHandle, configStore);
}
@UserIdInt int getCurrentUserProfileId() {
+ if (mConfigStore.isPrivateSpaceInPhotoPickerEnabled() && SdkLevel.isAtLeastS()) {
+ return mUserManagerState.getCurrentUserProfileId().getIdentifier();
+ }
return mUserIdManager.getCurrentUserProfileId().getIdentifier();
}
@@ -305,6 +325,12 @@
super(context, userIdManager, configStore);
}
+ CloudBannerManager(@NonNull Context context, @NonNull UserManagerState userManagerState,
+ @NonNull ConfigStore configStore) {
+ super(context, userManagerState, configStore);
+ }
+
+
/**
* Initialise and set the banner data for the current user.
*
diff --git a/src/com/android/providers/media/photopicker/viewmodel/PickerViewModel.java b/src/com/android/providers/media/photopicker/viewmodel/PickerViewModel.java
index 7208675..04dc7d5 100644
--- a/src/com/android/providers/media/photopicker/viewmodel/PickerViewModel.java
+++ b/src/com/android/providers/media/photopicker/viewmodel/PickerViewModel.java
@@ -24,6 +24,7 @@
import static android.provider.CloudMediaProviderContract.AlbumColumns.ALBUM_ID_SCREENSHOTS;
import static android.provider.CloudMediaProviderContract.AlbumColumns.ALBUM_ID_VIDEOS;
+import static com.android.providers.media.PickerUriResolver.INIT_PATH;
import static com.android.providers.media.PickerUriResolver.REFRESH_UI_PICKER_INTERNAL_OBSERVABLE_URI;
import static com.android.providers.media.photopicker.DataLoaderThread.TOKEN;
import static com.android.providers.media.photopicker.PickerSyncController.LOCAL_PICKER_PROVIDER_AUTHORITY;
@@ -79,8 +80,10 @@
import com.android.providers.media.photopicker.data.PaginationParameters;
import com.android.providers.media.photopicker.data.Selection;
import com.android.providers.media.photopicker.data.UserIdManager;
+import com.android.providers.media.photopicker.data.UserManagerState;
import com.android.providers.media.photopicker.data.model.Category;
import com.android.providers.media.photopicker.data.model.Item;
+import com.android.providers.media.photopicker.data.model.RefreshRequest;
import com.android.providers.media.photopicker.data.model.UserId;
import com.android.providers.media.photopicker.metrics.NonUiEventLogger;
import com.android.providers.media.photopicker.metrics.PhotoPickerUiEventLogger;
@@ -103,8 +106,6 @@
*/
public class PickerViewModel extends AndroidViewModel {
public static final String TAG = "PhotoPicker";
-
- private static final int RECENT_MINIMUM_COUNT = 12;
private static final int INSTANCE_ID_MAX = 1 << 15;
private static final int DELAY_MILLIS = 0;
@@ -139,11 +140,13 @@
private MutableLiveData<List<Category>> mCategoryList;
private MutableLiveData<Boolean> mIsAllPreGrantedMediaLoaded = new MutableLiveData<>(false);
- private final MutableLiveData<Boolean> mShouldRefreshUiLiveData = new MutableLiveData<>(false);
+ private final MutableLiveData<RefreshRequest> mRefreshUiLiveData =
+ new MutableLiveData<>(RefreshRequest.DEFAULT);
private final ContentObserver mRefreshUiNotificationObserver = new ContentObserver(null) {
@Override
- public void onChange(boolean selfChange) {
- mShouldRefreshUiLiveData.postValue(true);
+ public void onChange(boolean selfChange, Uri uri) {
+ boolean shouldInit = uri.getLastPathSegment().equals(INIT_PATH);
+ mRefreshUiLiveData.postValue(new RefreshRequest(true, shouldInit));
}
};
@@ -151,6 +154,7 @@
private ItemsProvider mItemsProvider;
private UserIdManager mUserIdManager;
+ private UserManagerState mUserManagerState;
private BannerManager mBannerManager;
private InstanceId mInstanceId;
@@ -179,7 +183,6 @@
mAppContext = application.getApplicationContext();
mItemsProvider = new ItemsProvider(mAppContext);
mSelection = new Selection();
- mUserIdManager = UserIdManager.create(mAppContext);
mMuteStatus = new MuteStatus();
mInstanceId = new InstanceIdSequence(INSTANCE_ID_MAX).newInstanceId();
mLogger = new PhotoPickerUiEventLogger();
@@ -188,6 +191,13 @@
mIsLocalOnly = false;
initConfigStore();
+ if (mConfigStore.isPrivateSpaceInPhotoPickerEnabled() && SdkLevel.isAtLeastS()) {
+ mUserManagerState = UserManagerState.create(mAppContext);
+ mUserIdManager = null;
+ } else {
+ mUserIdManager = UserIdManager.create(mAppContext);
+ mUserManagerState = null;
+ }
// When the user opens the PhotoPickerSettingsActivity and changes the cloud provider, it's
// possible that system kills PhotoPickerActivity and PickerViewModel while it's in the
@@ -248,9 +258,23 @@
@VisibleForTesting
public void setUserIdManager(@NonNull UserIdManager userIdManager) {
+ if (userIdManager == null) {
+ throw new IllegalArgumentException("Given UserIdManager object can not be null");
+ }
mUserIdManager = userIdManager;
}
+ /**
+ * Injects given {@link UserManagerState} object into {@link #mUserManagerState}
+ */
+ @VisibleForTesting
+ public void setUserManagerState(@NonNull UserManagerState userManagerState) {
+ if (userManagerState == null) {
+ throw new IllegalArgumentException("Given UserManagerState object can not be null");
+ }
+ mUserManagerState = userManagerState;
+ }
+
@VisibleForTesting
public void setBannerManager(@NonNull BannerManager bannerManager) {
mBannerManager = bannerManager;
@@ -290,6 +314,13 @@
}
/**
+ * @return {@link UserManagerState} for this context.
+ */
+ public UserManagerState getUserManagerState() {
+ return mUserManagerState;
+ }
+
+ /**
* @return {@code mSelection} that manages the selection
*/
public Selection getSelection() {
@@ -367,27 +398,52 @@
}
/**
+ * Reset to a given profile
+ * @param userId : the profile where photopicker want switch to
+ */
+ @UiThread
+ public void resetToGivenUserProfile(@NonNull UserId userId) {
+ if (mConfigStore.isPrivateSpaceInPhotoPickerEnabled() && SdkLevel.isAtLeastS()) {
+ if (userId == null) {
+ throw new IllegalArgumentException("Given userId can not be null");
+ }
+ mUserManagerState.setUserAsCurrentUserProfile(userId);
+ onSwitchedProfile();
+ }
+ }
+
+ /**
+ * Reset to a user profile that starts photopicker activity
+ */
+ @UiThread
+ public void resetToCurrentUserProfile() {
+ resetToGivenUserProfile(UserId.CURRENT_USER);
+ }
+
+ /**
* Reset the content observer & all the content on profile switched.
*/
@UiThread
public void onSwitchedProfile() {
resetRefreshUiNotificationObserver();
- resetAllContentInCurrentProfile();
+ resetAllContentInCurrentProfile(/* shouldSendInitRequest */ true);
}
/**
* Reset all the content (items, categories & banners) in the current profile.
*/
@UiThread
- public void resetAllContentInCurrentProfile() {
+ public void resetAllContentInCurrentProfile(boolean shouldSendInitRequest) {
Log.d(TAG, "Reset all content in current profile");
// Post 'should refresh UI live data' value as false to avoid unnecessary repetitive resets
- mShouldRefreshUiLiveData.postValue(false);
+ mRefreshUiLiveData.postValue(RefreshRequest.DEFAULT);
clearQueuedTasksInDataLoaderThread();
- initPhotoPickerData();
+ if (shouldSendInitRequest) {
+ initPhotoPickerData();
+ }
// Clear the existing content - selection, photos grid, albums grid, banners
mSelection.clearSelectedItems();
@@ -500,6 +556,13 @@
loadItemsAsync(pagingParameters, /* isReset */ isReset, action);
}
+ private UserId getCurrentUserProfileId() {
+ if (mConfigStore.isPrivateSpaceInPhotoPickerEnabled() && SdkLevel.isAtLeastS()) {
+ return mUserManagerState.getCurrentUserProfileId();
+ }
+ return mUserIdManager.getCurrentUserProfileId();
+ }
+
/**
* Loads required items and sets it to the {@link PickerViewModel#mItemsResult} while
* considering the isReset value.
@@ -511,8 +574,7 @@
*/
private void loadItemsAsync(@NonNull PaginationParameters pagingParameters, boolean isReset,
@ItemsAction.Type int action) {
- final UserId userId = mUserIdManager.getCurrentUserProfileId();
-
+ final UserId userId = getCurrentUserProfileId();
DataLoaderThread.getHandler().postDelayed(() -> {
// Load the items as per the pagination parameters passed as params to this method.
List<Item> newPageItemList = loadItems(Category.DEFAULT, userId, pagingParameters);
@@ -611,7 +673,7 @@
idsForItemsToBeFetched.removeAll(mSelection.getPreGrantedItemIdsToBeRevoked());
if (!idsForItemsToBeFetched.isEmpty()) {
- final UserId userId = mUserIdManager.getCurrentUserProfileId();
+ UserId userId = getCurrentUserProfileId();
DataLoaderThread.getHandler().postDelayed(() -> {
loadItemsWithLocalIdSelection(Category.DEFAULT, userId,
idsForItemsToBeFetched.stream().map(Integer::valueOf).collect(
@@ -738,7 +800,7 @@
*/
private void loadCategoryItemsAsync(PaginationParameters pagingParameters, boolean isReset,
@ItemsAction.Type int action) {
- final UserId userId = mUserIdManager.getCurrentUserProfileId();
+ final UserId userId = getCurrentUserProfileId();
final Category category = mCurrentCategory;
DataLoaderThread.getHandler().postDelayed(() -> {
@@ -835,7 +897,7 @@
}
private void loadCategoriesAsync() {
- final UserId userId = mUserIdManager.getCurrentUserProfileId();
+ final UserId userId = getCurrentUserProfileId();
DataLoaderThread.getHandler().postDelayed(() -> {
mCategoryList.postValue(loadCategories(userId));
}, TOKEN, DELAY_MILLIS);
@@ -889,8 +951,11 @@
}
}
}
- mUserIdManager.setIntentAndCheckRestrictions(intent);
-
+ if (mConfigStore.isPrivateSpaceInPhotoPickerEnabled() && SdkLevel.isAtLeastS()) {
+ mUserManagerState.setIntentAndCheckRestrictions(intent);
+ } else {
+ mUserIdManager.setIntentAndCheckRestrictions(intent);
+ }
mMimeTypeFilters = MimeFilterUtils.getMimeTypeFilters(intent);
mSelection.parseSelectionValuesFromIntent(intent);
@@ -925,15 +990,24 @@
}
}
+
private boolean checkPickerLaunchOptionValidity(int launchOption) {
return launchOption == MediaStore.PICK_IMAGES_TAB_IMAGES
|| launchOption == MediaStore.PICK_IMAGES_TAB_ALBUMS;
}
private void initBannerManager() {
- mBannerManager = shouldShowOnlyLocalFeatures()
- ? new BannerManager(mAppContext, mUserIdManager, mConfigStore)
- : new BannerManager.CloudBannerManager(mAppContext, mUserIdManager, mConfigStore);
+ if (mConfigStore.isPrivateSpaceInPhotoPickerEnabled() && SdkLevel.isAtLeastS()) {
+ mBannerManager = shouldShowOnlyLocalFeatures()
+ ? new BannerManager(mAppContext, mUserManagerState, mConfigStore)
+ : new BannerManager.CloudBannerManager(
+ mAppContext, mUserManagerState, mConfigStore);
+ } else {
+ mBannerManager = shouldShowOnlyLocalFeatures()
+ ? new BannerManager(mAppContext, mUserIdManager, mConfigStore)
+ : new BannerManager.CloudBannerManager(
+ mAppContext, mUserIdManager, mConfigStore);
+ }
}
/**
@@ -954,6 +1028,10 @@
* Log picker opened metrics
*/
public void logPickerOpened(int callingUid, String callingPackage, String intentAction) {
+ if (mConfigStore.isPrivateSpaceInPhotoPickerEnabled() && SdkLevel.isAtLeastS()) {
+ return;
+ }
+ //Todo(b/318614654): need to refactor
if (getUserIdManager().isManagedUserSelected()) {
mLogger.logPickerOpenWork(mInstanceId, callingUid, callingPackage);
} else {
@@ -1045,6 +1123,10 @@
* Log metrics to notify that the user has confirmed selection
*/
public void logPickerConfirm(int callingUid, String callingPackage, int countOfItemsConfirmed) {
+ if (mConfigStore.isPrivateSpaceInPhotoPickerEnabled() && SdkLevel.isAtLeastS()) {
+ return;
+ }
+ //Todo(b/318614654): need to refactor
if (getUserIdManager().isManagedUserSelected()) {
mLogger.logPickerConfirmWork(mInstanceId, callingUid, callingPackage,
countOfItemsConfirmed);
@@ -1058,6 +1140,10 @@
* Log metrics to notify that the user has exited Picker without any selection
*/
public void logPickerCancel(int callingUid, String callingPackage) {
+ if (mConfigStore.isPrivateSpaceInPhotoPickerEnabled() && SdkLevel.isAtLeastS()) {
+ return;
+ }
+ //Todo(b/318614654): need to refactor
if (getUserIdManager().isManagedUserSelected()) {
mLogger.logPickerCancelWork(mInstanceId, callingUid, callingPackage);
} else {
@@ -1429,14 +1515,14 @@
* @return a {@link LiveData} that posts Should Refresh Picker UI as {@code true} when notified.
*/
@NonNull
- public LiveData<Boolean> shouldRefreshUiLiveData() {
- return mShouldRefreshUiLiveData;
+ public LiveData<RefreshRequest> refreshUiLiveData() {
+ return mRefreshUiLiveData;
}
private void registerRefreshUiNotificationObserver() {
mContentResolver = getContentResolverForSelectedUser();
mContentResolver.registerContentObserver(REFRESH_UI_PICKER_INTERNAL_OBSERVABLE_URI,
- /* notifyForDescendants */ false, mRefreshUiNotificationObserver);
+ /* notifyForDescendants */ true, mRefreshUiNotificationObserver);
}
private void unregisterRefreshUiNotificationObserver() {
@@ -1452,7 +1538,7 @@
}
private ContentResolver getContentResolverForSelectedUser() {
- final UserId selectedUserId = mUserIdManager.getCurrentUserProfileId();
+ final UserId selectedUserId = getCurrentUserProfileId();
if (selectedUserId == null) {
Log.d(TAG, "Selected user id is NULL; returning the default content resolver.");
return mAppContext.getContentResolver();
@@ -1508,7 +1594,7 @@
*/
public void initPhotoPickerData(@NonNull Category category) {
if (mConfigStore.isCloudMediaInPhotoPickerEnabled()) {
- UserId userId = mUserIdManager.getCurrentUserProfileId();
+ final UserId userId = getCurrentUserProfileId();
DataLoaderThread.getHandler().postDelayed(() -> {
if (category == Category.DEFAULT) {
mIsSyncInProgress.postValue(true);
diff --git a/src/com/android/providers/media/util/Memory.java b/src/com/android/providers/media/util/Memory.java
index f2306e3..d602953 100644
--- a/src/com/android/providers/media/util/Memory.java
+++ b/src/com/android/providers/media/util/Memory.java
@@ -19,6 +19,11 @@
import java.nio.ByteOrder;
public class Memory {
+
+ private Memory() {
+ // Utility class, cannot be instantiated
+ }
+
public static int peekInt(byte[] src, int offset, ByteOrder order) {
if (order == ByteOrder.BIG_ENDIAN) {
return (((src[offset++] & 0xff) << 24) |
diff --git a/src/com/android/providers/media/util/Metrics.java b/src/com/android/providers/media/util/Metrics.java
index b97df56..be0d5e5 100644
--- a/src/com/android/providers/media/util/Metrics.java
+++ b/src/com/android/providers/media/util/Metrics.java
@@ -43,6 +43,11 @@
* regression investigations and bug triage.
*/
public class Metrics {
+
+ private Metrics() {
+ // Utility class, cannot be instantiated
+ }
+
public static void logScan(@NonNull String volumeName, int reason, long itemCount,
long durationMillis, int insertCount, int updateCount, int deleteCount) {
Logging.logPersistent(
diff --git a/src/com/android/providers/media/util/PermissionUtils.java b/src/com/android/providers/media/util/PermissionUtils.java
index 063d9d7..40e05d9 100644
--- a/src/com/android/providers/media/util/PermissionUtils.java
+++ b/src/com/android/providers/media/util/PermissionUtils.java
@@ -22,6 +22,7 @@
import static android.Manifest.permission.INSTALL_PACKAGES;
import static android.Manifest.permission.MANAGE_EXTERNAL_STORAGE;
import static android.Manifest.permission.MANAGE_MEDIA;
+import static android.Manifest.permission.QUERY_ALL_PACKAGES;
import static android.Manifest.permission.READ_EXTERNAL_STORAGE;
import static android.Manifest.permission.READ_MEDIA_AUDIO;
import static android.Manifest.permission.READ_MEDIA_IMAGES;
@@ -48,6 +49,7 @@
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.os.UserHandle;
+import android.provider.MediaStore;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
@@ -96,6 +98,14 @@
}
/**
+ * @return {@code true} if the given {@code uid} is {@link android.os.Process#SYSTEM_UID},
+ * {@code false} otherwise.
+ */
+ public static boolean checkPermissionSystem(int uid) {
+ return UserHandle.getAppId(uid) == android.os.Process.SYSTEM_UID;
+ }
+
+ /**
* Check if the given package has been granted the "file manager" role on
* the device, which should grant them certain broader access.
*/
@@ -197,8 +207,8 @@
}
public static boolean checkIsLegacyStorageGranted(@NonNull Context context, int uid,
- String packageName, @Nullable String attributionTag) {
- if (context.getSystemService(AppOpsManager.class)
+ String packageName, @Nullable String attributionTag, boolean isTargetSdkAtLeastR) {
+ if (!isTargetSdkAtLeastR && context.getSystemService(AppOpsManager.class)
.unsafeCheckOp(OPSTR_LEGACY_STORAGE, uid, packageName) == MODE_ALLOWED) {
return true;
}
@@ -315,6 +325,27 @@
generateAppOpMessage(packageName, sOpDescription.get()));
}
+ /**
+ * Check if the given package has been granted the
+ * android.Manifest.permission#QUERY_ALL_PACKAGES permission.
+ */
+ public static boolean checkPermissionQueryAllPackages(@NonNull Context context, int pid,
+ int uid, @NonNull String packageName, @Nullable String attributionTag) {
+ return checkPermissionForDataDelivery(context, QUERY_ALL_PACKAGES, pid,
+ uid, packageName, attributionTag, null);
+ }
+
+ /**
+ * Check if the given package has been granted the
+ * android.provider.MediaStore.#ACCESS_MEDIA_OWNER_PACKAGE_NAME_PERMISSION permission.
+ */
+ public static boolean checkPermissionAccessMediaOwnerPackageName(@NonNull Context context,
+ int pid, int uid, @NonNull String packageName, @Nullable String attributionTag) {
+ return checkPermissionForDataDelivery(context,
+ MediaStore.ACCESS_MEDIA_OWNER_PACKAGE_NAME_PERMISSION,
+ pid, uid, packageName, attributionTag, null);
+ }
+
public static boolean checkPermissionInstallPackages(@NonNull Context context, int pid, int uid,
@NonNull String packageName, @Nullable String attributionTag) {
return checkPermissionForDataDelivery(context, INSTALL_PACKAGES, pid,
diff --git a/src/com/android/providers/media/util/SQLiteQueryBuilder.java b/src/com/android/providers/media/util/SQLiteQueryBuilder.java
index cedd353..ab1feb9 100644
--- a/src/com/android/providers/media/util/SQLiteQueryBuilder.java
+++ b/src/com/android/providers/media/util/SQLiteQueryBuilder.java
@@ -47,6 +47,7 @@
import com.google.common.base.Strings;
+import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Iterator;
@@ -86,7 +87,7 @@
"(?i)custom_[a-zA-Z]+");
private Map<String, String> mProjectionMap = null;
- private Collection<Pattern> mProjectionGreylist = null;
+ private Collection<Pattern> mProjectionAllowlist = null;
private String mTables = "";
private StringBuilder mWhereClause = null; // lazily created
@@ -121,6 +122,34 @@
}
/**
+ * Constructs a new SQLiteQueryBuilder instance as a copy of existing one.
+ *
+ * @param qb an instance to copy.
+ */
+ public SQLiteQueryBuilder(SQLiteQueryBuilder qb) {
+ if (qb != null) {
+ if (qb.mProjectionMap != null) {
+ final ArrayMap<String, String> projectionMapCopy = new ArrayMap<>();
+ projectionMapCopy.putAll(qb.mProjectionMap);
+ this.mProjectionMap = projectionMapCopy;
+ }
+
+ if (qb.mProjectionAllowlist != null) {
+ this.mProjectionAllowlist = new ArrayList<>(qb.mProjectionAllowlist);
+ }
+
+ if (qb.mWhereClause != null) {
+ this.mWhereClause = new StringBuilder(qb.mWhereClause);
+ }
+
+ this.mTables = qb.mTables;
+ this.mDistinct = qb.mDistinct;
+ this.mStrictFlags = qb.mStrictFlags;
+ this.mTargetSdkVersion = qb.mTargetSdkVersion;
+ }
+ }
+
+ /**
* Mark the query as {@code DISTINCT}.
*
* @param distinct if true the query is {@code DISTINCT}, otherwise it isn't
@@ -251,20 +280,20 @@
}
/**
- * Sets a projection greylist of columns that will be allowed through, even
+ * Sets a projection allowlist of columns that will be allowed through, even
* when {@link #setStrict(boolean)} is enabled. This provides a way for
* abusive custom columns like {@code COUNT(*)} to continue working.
*/
- public void setProjectionGreylist(@Nullable Collection<Pattern> projectionGreylist) {
- mProjectionGreylist = projectionGreylist;
+ public void setProjectionAllowlist(@Nullable Collection<Pattern> projectionAllowlist) {
+ mProjectionAllowlist = projectionAllowlist;
}
/**
- * Gets the projection greylist for the query, as last configured by
- * {@link #setProjectionGreylist}.
+ * Gets the projection allowlist for the query, as last configured by
+ * {@link #setProjectionAllowlist}.
*/
- public @Nullable Collection<Pattern> getProjectionGreylist() {
- return mProjectionGreylist;
+ public @Nullable Collection<Pattern> getProjectionAllowlist() {
+ return mProjectionAllowlist;
}
/**
@@ -1023,8 +1052,14 @@
projectionOut[i] = computeSingleProjectionOrThrow(projectionIn[i]);
}
return projectionOut;
- } else if (mProjectionMap != null) {
- // Return all columns in projection map.
+ } else {
+ return getAllColumnsFromProjectionMap();
+ }
+ }
+
+ /** {@hide} */
+ public String[] getAllColumnsFromProjectionMap() {
+ if (mProjectionMap != null) {
Set<Entry<String, String>> entrySet = mProjectionMap.entrySet();
String[] projection = new String[entrySet.size()];
Iterator<Entry<String, String>> entryIter = entrySet.iterator();
@@ -1085,11 +1120,11 @@
return maybeWithOperator(operator, userColumn);
}
- // If greylist is configured, we might be willing to let
+ // If allowlist is configured, we might be willing to let
// this custom column bypass our strict checks.
- if (mProjectionGreylist != null) {
+ if (mProjectionAllowlist != null) {
boolean match = false;
- for (Pattern p : mProjectionGreylist) {
+ for (Pattern p : mProjectionAllowlist) {
if (p.matcher(userColumn).matches()) {
match = true;
break;
diff --git a/tests/src/com/android/providers/media/ConfigStoreTest.java b/tests/src/com/android/providers/media/ConfigStoreTest.java
index 10728b8..5286798 100644
--- a/tests/src/com/android/providers/media/ConfigStoreTest.java
+++ b/tests/src/com/android/providers/media/ConfigStoreTest.java
@@ -71,5 +71,6 @@
assertTrue(mConfigStore.shouldPickerPreloadForPickImages());
assertFalse(mConfigStore.shouldPickerRespectPreloadArgumentForPickImages());
assertFalse(mConfigStore.shouldTranscodeDefault());
+ assertFalse(mConfigStore.isPrivateSpaceInPhotoPickerEnabled());
}
}
diff --git a/tests/src/com/android/providers/media/MediaProviderTest.java b/tests/src/com/android/providers/media/MediaProviderTest.java
index fe13649..d82695c 100644
--- a/tests/src/com/android/providers/media/MediaProviderTest.java
+++ b/tests/src/com/android/providers/media/MediaProviderTest.java
@@ -1643,7 +1643,7 @@
}
private static boolean isGreylistMatch(String raw) {
- for (Pattern p : MediaProvider.sGreylist) {
+ for (Pattern p : MediaProvider.sAllowlist) {
if (p.matcher(raw).matches()) {
return true;
}
diff --git a/tests/src/com/android/providers/media/TestConfigStore.java b/tests/src/com/android/providers/media/TestConfigStore.java
index ca38ecc..c3c71ba 100644
--- a/tests/src/com/android/providers/media/TestConfigStore.java
+++ b/tests/src/com/android/providers/media/TestConfigStore.java
@@ -35,6 +35,7 @@
*/
public class TestConfigStore implements ConfigStore {
private boolean mCloudMediaInPhotoPickerEnabled = false;
+ private boolean mPrivateSpaceEnabled = false;
private boolean mPickerChoiceManagedSelectionEnabled = false;
private List<String> mAllowedCloudProviderPackages = Collections.emptyList();
@@ -47,6 +48,26 @@
notifyObservers();
}
+ /**
+ * Enables private space flag for PhotoPicker in test config
+ */
+ public void enablePrivateSpaceInPhotoPicker() {
+ mPrivateSpaceEnabled = true;
+ notifyObservers();
+ }
+
+ /**
+ * Disables private space flag for PhotoPicker in test config
+ */
+ public void disablePrivateSpaceInPhotoPicker() {
+ mPrivateSpaceEnabled = false;
+ notifyObservers();
+ }
+ @Override
+ public boolean isPrivateSpaceInPhotoPickerEnabled() {
+ return mPrivateSpaceEnabled;
+ }
+
public void enableCloudMediaFeature() {
mCloudMediaInPhotoPickerEnabled = true;
notifyObservers();
diff --git a/tests/src/com/android/providers/media/TranscodeHelperNoOpTest.java b/tests/src/com/android/providers/media/TranscodeHelperNoOpTest.java
new file mode 100644
index 0000000..1b832ee
--- /dev/null
+++ b/tests/src/com/android/providers/media/TranscodeHelperNoOpTest.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2024 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.providers.media;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.net.Uri;
+
+import org.junit.Test;
+
+/**
+ * This class exists mostly for code coverage purposes
+ */
+public class TranscodeHelperNoOpTest {
+
+ private static final String TEST_STRING = "foo";
+ private final TranscodeHelperNoOp mTranscodeHelper = new TranscodeHelperNoOp();
+
+ @Test
+ public void testBasicFunctionality() {
+ mTranscodeHelper.freeCache(1L);
+ mTranscodeHelper.onAnrDelayStarted(TEST_STRING, 0, 0, 0);
+ assertThat(mTranscodeHelper.transcode(TEST_STRING, TEST_STRING, 0, 0)).isFalse();
+ assertThat(mTranscodeHelper.prepareIoPath(TEST_STRING, 0)).isNull();
+ assertThat(mTranscodeHelper.shouldTranscode(TEST_STRING, 0, null)).isEqualTo(0);
+ mTranscodeHelper.onUriPublished(Uri.EMPTY);
+ assertThat(mTranscodeHelper.supportsTranscode(TEST_STRING)).isFalse();
+ mTranscodeHelper.onUriPublished(Uri.EMPTY);
+ mTranscodeHelper.onFileOpen(TEST_STRING, TEST_STRING, 0, 0);
+ assertThat(mTranscodeHelper.isTranscodeFileCached(TEST_STRING, TEST_STRING)).isFalse();
+ assertThat(mTranscodeHelper.deleteCachedTranscodeFile(1L)).isFalse();
+ mTranscodeHelper.dump(null);
+ assertThat(mTranscodeHelper.getSupportedRelativePaths()).isEmpty();
+ }
+}
diff --git a/tests/src/com/android/providers/media/metrics/TranscodeMetricsTest.java b/tests/src/com/android/providers/media/metrics/TranscodeMetricsTest.java
index 8b635df..4651547 100644
--- a/tests/src/com/android/providers/media/metrics/TranscodeMetricsTest.java
+++ b/tests/src/com/android/providers/media/metrics/TranscodeMetricsTest.java
@@ -20,7 +20,7 @@
import android.util.StatsEvent;
-import androidx.test.runner.AndroidJUnit4;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import org.junit.After;
import org.junit.Test;
@@ -30,7 +30,6 @@
@RunWith(AndroidJUnit4.class)
public class TranscodeMetricsTest {
- private static final int UNKNOWN_ATOM_TAG = -1;
private static final TranscodeMetrics.TranscodingStatsData EMPTY_STATS_DATA =
new TranscodeMetrics.TranscodingStatsData(
diff --git a/tests/src/com/android/providers/media/photopicker/PickerDataLayerTest.java b/tests/src/com/android/providers/media/photopicker/PickerDataLayerTest.java
index 5f6f269..fe92dfc 100644
--- a/tests/src/com/android/providers/media/photopicker/PickerDataLayerTest.java
+++ b/tests/src/com/android/providers/media/photopicker/PickerDataLayerTest.java
@@ -59,7 +59,6 @@
import org.junit.After;
import org.junit.Before;
-import org.junit.Ignore;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -403,7 +402,6 @@
}
@Test
- @Ignore("Enable when b/293112236 is done")
public void testFetchAlbumMedia() {
mController.setCloudProvider(CLOUD_PRIMARY_PROVIDER_AUTHORITY);
@@ -476,7 +474,6 @@
}
@Test
- @Ignore("Enable when b/293112236 is done")
public void testFetchAlbumMediaMimeTypeFilter() {
mController.setCloudProvider(CLOUD_PRIMARY_PROVIDER_AUTHORITY);
@@ -497,11 +494,10 @@
mDataLayer.initMediaData(syncRequestExtras);
try (Cursor cr = mDataLayer.fetchAllAlbums(mimeTypeQueryArgs)) {
- assertThat(cr.getCount()).isEqualTo(4);
+ assertThat(cr.getCount()).isEqualTo(3);
- // Favorites and Videos merged albums will be always visible
+ // Favorites album will be always visible
assertAlbumCursor(cr, ALBUM_ID_FAVORITES, LOCAL_PROVIDER_AUTHORITY);
- assertAlbumCursor(cr, ALBUM_ID_VIDEOS, LOCAL_PROVIDER_AUTHORITY);
assertAlbumCursor(cr, ALBUM_ID_1, LOCAL_PROVIDER_AUTHORITY);
assertAlbumCursor(cr, ALBUM_ID_2, CLOUD_PRIMARY_PROVIDER_AUTHORITY);
}
@@ -527,7 +523,6 @@
}
@Test
- @Ignore("Enable when b/293112236 is done")
public void testFetchAlbumMediaSizeFilter() {
mController.setCloudProvider(CLOUD_PRIMARY_PROVIDER_AUTHORITY);
@@ -552,7 +547,7 @@
try (Cursor cr = mDataLayer.fetchAllAlbums(sizeQueryArgs)) {
assertThat(cr.getCount()).isEqualTo(4);
- // Favorites and Videos merged albums will be always visible
+ // Favorites album will be always visible
assertAlbumCursor(cr, ALBUM_ID_FAVORITES, LOCAL_PROVIDER_AUTHORITY);
assertAlbumCursor(cr, ALBUM_ID_VIDEOS, LOCAL_PROVIDER_AUTHORITY);
assertAlbumCursor(cr, ALBUM_ID_1, LOCAL_PROVIDER_AUTHORITY);
@@ -580,7 +575,6 @@
}
@Test
- @Ignore("Enable when b/293112236 is done")
public void testFetchAlbumMediaMimeTypeAndSizeFilter() {
mController.setCloudProvider(CLOUD_PRIMARY_PROVIDER_AUTHORITY);
@@ -608,7 +602,7 @@
// Most recent video will be the cover of the Videos album. In this scenario, Videos
// album cover was generated with cloud authority, so the Videos album authority should
// be cloud provider authority.
- // Favorites and Videos album will always be displayed.
+ // Favorites album will always be displayed.
assertAlbumCursor(cr, ALBUM_ID_FAVORITES, LOCAL_PROVIDER_AUTHORITY);
assertAlbumCursor(cr, ALBUM_ID_VIDEOS, CLOUD_PRIMARY_PROVIDER_AUTHORITY);
assertAlbumCursor(cr, ALBUM_ID_1, LOCAL_PROVIDER_AUTHORITY);
@@ -628,7 +622,6 @@
}
@Test
- @Ignore("Enable when b/293112236 is done")
public void testFetchAlbumMediaLocalOnly() {
mController.setCloudProvider(CLOUD_PRIMARY_PROVIDER_AUTHORITY);
diff --git a/tests/src/com/android/providers/media/photopicker/PickerSyncControllerTest.java b/tests/src/com/android/providers/media/photopicker/PickerSyncControllerTest.java
index 7408e4b..2c4d930 100644
--- a/tests/src/com/android/providers/media/photopicker/PickerSyncControllerTest.java
+++ b/tests/src/com/android/providers/media/photopicker/PickerSyncControllerTest.java
@@ -17,6 +17,7 @@
package com.android.providers.media.photopicker;
import static com.android.providers.media.PickerProviderMediaGenerator.MediaGenerator;
+import static com.android.providers.media.PickerUriResolver.INIT_PATH;
import static com.android.providers.media.PickerUriResolver.REFRESH_UI_PICKER_INTERNAL_OBSERVABLE_URI;
import static com.android.providers.media.photopicker.NotificationContentObserver.MEDIA;
@@ -36,6 +37,7 @@
import android.content.res.Resources;
import android.database.ContentObserver;
import android.database.Cursor;
+import android.net.Uri;
import android.os.Handler;
import android.os.Process;
import android.os.storage.StorageManager;
@@ -48,6 +50,7 @@
import androidx.test.platform.app.InstrumentationRegistry;
import com.android.providers.media.PickerProviderMediaGenerator;
+import com.android.providers.media.R;
import com.android.providers.media.TestConfigStore;
import com.android.providers.media.photopicker.data.CloudProviderInfo;
import com.android.providers.media.photopicker.data.PickerDatabaseHelper;
@@ -77,6 +80,7 @@
private static final String CLOUD_SECONDARY_PROVIDER_AUTHORITY =
"com.android.providers.media.photopicker.tests.cloud_secondary";
private static final String PACKAGE_NAME = "com.android.providers.media.tests";
+ private static final String APP_LABEL = "MediaProvider Tests";
private final MediaGenerator mLocalMediaGenerator =
PickerProviderMediaGenerator.getMediaGenerator(LOCAL_PROVIDER_AUTHORITY);
@@ -743,7 +747,7 @@
}
@Test
- public void testSetCloudProvider() {
+ public void testSetCloudProvider() throws UnableToAcquireLockException {
//1. Get local provider assertion out of the way
assertWithMessage("Unexpected local provider.")
.that(mController.getLocalProvider()).isEqualTo(LOCAL_PROVIDER_AUTHORITY);
@@ -757,6 +761,8 @@
.that(mController.setCloudProvider(CLOUD_PRIMARY_PROVIDER_AUTHORITY)).isTrue();
assertWithMessage("Unexpected cloud provider.")
.that(mController.getCloudProvider()).isEqualTo(CLOUD_PRIMARY_PROVIDER_AUTHORITY);
+ assertWithMessage("Unexpected cloud provider label.")
+ .that(mController.getCurrentCloudProviderLocalizedLabel()).isEqualTo(APP_LABEL);
// Assert that setting cloud provider clears facade cloud provider
// And after syncing, the latest provider is set on the facade
@@ -771,6 +777,11 @@
.that(setCloudProviderAndSyncAllMedia(null)).isTrue();
assertWithMessage("Cloud provider should have been null.")
.that(mController.getCloudProvider()).isNull();
+ final String noProviderLabel = mContext.getResources()
+ .getString(R.string.picker_settings_no_provider);
+ assertWithMessage("Unexpected cloud provider label.")
+ .that(mController.getCurrentCloudProviderLocalizedLabel())
+ .isEqualTo(noProviderLabel);
// Assert that setting cloud provider clears facade cloud provider
// And after syncing, the latest provider is set on the facade
@@ -785,6 +796,8 @@
.that(mController.setCloudProvider(CLOUD_PRIMARY_PROVIDER_AUTHORITY)).isTrue();
assertWithMessage("Unexpected cloud provider.")
.that(mController.getCloudProvider()).isEqualTo(CLOUD_PRIMARY_PROVIDER_AUTHORITY);
+ assertWithMessage("Unexpected cloud provider label.")
+ .that(mController.getCurrentCloudProviderLocalizedLabel()).isEqualTo(APP_LABEL);
// Assert that setting cloud provider clears facade cloud provider
// And after syncing, the latest provider is set on the facade
@@ -1725,7 +1738,7 @@
// Simulate a UI session begins listening.
contentResolver.registerContentObserver(REFRESH_UI_PICKER_INTERNAL_OBSERVABLE_URI,
- /* notifyForDescendants */ false, refreshUiNotificationObserver);
+ /* notifyForDescendants */ true, refreshUiNotificationObserver);
mCloudPrimaryMediaGenerator.setMediaCollectionId(COLLECTION_2);
@@ -1733,6 +1746,11 @@
assertWithMessage("Refresh ui notification should have been received.")
.that(refreshUiNotificationObserver.mNotificationReceived).isTrue();
+
+ assertWithMessage("Refresh ui notification uri should not include init path.")
+ .that(refreshUiNotificationObserver.mNotificationUri.getLastPathSegment()
+ .equals(INIT_PATH))
+ .isFalse();
} finally {
contentResolver.unregisterContentObserver(refreshUiNotificationObserver);
}
@@ -1744,7 +1762,7 @@
final TestContentObserver refreshUiNotificationObserver = new TestContentObserver(null);
try {
contentResolver.registerContentObserver(REFRESH_UI_PICKER_INTERNAL_OBSERVABLE_URI,
- /* notifyForDescendants */ false, refreshUiNotificationObserver);
+ /* notifyForDescendants */ true, refreshUiNotificationObserver);
assertWithMessage("Refresh ui notification should have not been received.")
.that(refreshUiNotificationObserver.mNotificationReceived).isFalse();
@@ -1760,7 +1778,12 @@
"Failed to receive refresh ui notification on change in cloud provider.")
.that(refreshUiNotificationObserver.mNotificationReceived).isTrue();
- refreshUiNotificationObserver.mNotificationReceived = false;
+ assertWithMessage("Refresh ui notification uri should not include init path.")
+ .that(refreshUiNotificationObserver.mNotificationUri.getLastPathSegment()
+ .equals(INIT_PATH))
+ .isTrue();
+
+ refreshUiNotificationObserver.clear();
// The SET_CLOUD_PROVIDER is called using a different cloud provider from before
mController.setCloudProvider(CLOUD_SECONDARY_PROVIDER_AUTHORITY);
@@ -1769,7 +1792,12 @@
"Failed to receive refresh ui notification on change in cloud provider.")
.that(refreshUiNotificationObserver.mNotificationReceived).isTrue();
- refreshUiNotificationObserver.mNotificationReceived = false;
+ assertWithMessage("Refresh ui notification uri should not include init path.")
+ .that(refreshUiNotificationObserver.mNotificationUri.getLastPathSegment()
+ .equals(INIT_PATH))
+ .isTrue();
+
+ refreshUiNotificationObserver.clear();
// The cloud provider remains unchanged on PickerSyncController construction
mController = PickerSyncController
@@ -1879,14 +1907,21 @@
private static class TestContentObserver extends ContentObserver {
boolean mNotificationReceived;
+ Uri mNotificationUri;
TestContentObserver(Handler handler) {
super(handler);
}
@Override
- public void onChange(boolean selfChange) {
+ public void onChange(boolean selfChange, Uri uri) {
mNotificationReceived = true;
+ mNotificationUri = uri;
+ }
+
+ public void clear() {
+ mNotificationReceived = false;
+ mNotificationUri = null;
}
}
}
diff --git a/tests/src/com/android/providers/media/photopicker/data/UserManagerStateTest.java b/tests/src/com/android/providers/media/photopicker/data/UserManagerStateTest.java
new file mode 100644
index 0000000..2807da3
--- /dev/null
+++ b/tests/src/com/android/providers/media/photopicker/data/UserManagerStateTest.java
@@ -0,0 +1,352 @@
+/*
+ * Copyright (C) 2021 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.providers.media.photopicker.data;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.junit.Assert.assertThrows;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.content.pm.UserProperties;
+import android.os.UserHandle;
+import android.os.UserManager;
+
+import androidx.test.filters.SdkSuppress;
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import com.android.modules.utils.build.SdkLevel;
+import com.android.providers.media.photopicker.data.model.UserId;
+
+import org.junit.Before;
+import org.junit.Test;
+
+import java.util.Arrays;
+import java.util.List;
+
+@SdkSuppress(minSdkVersion = 31, codeName = "S")
+public class UserManagerStateTest {
+ private final UserHandle mPersonalUser = UserHandle.SYSTEM;
+ private final UserHandle mManagedUser = UserHandle.of(100); // like a managed profile
+ private final UserHandle mOtherUser1 = UserHandle.of(101); // like a private profile
+ private final UserHandle mOtherUser2 = UserHandle.of(102); // like a clone profile
+ private final UserHandle mOtherUser3 = UserHandle.of(103); // like a invalid user
+ private final Context mMockContext = mock(Context.class);
+ private final UserManager mMockUserManager = mock(UserManager.class);
+ private final PackageManager mMockPackageManager = mock(PackageManager.class);
+ private UserManagerState mUserManagerState;
+
+ @Before
+ public void setUp() throws Exception {
+ when(mMockContext.getApplicationContext()).thenReturn(mMockContext);
+
+ // set Managed Profile identification
+ when(mMockUserManager.isManagedProfile(
+ mManagedUser.getIdentifier())).thenReturn(true);
+ when(mMockUserManager.isManagedProfile(
+ mPersonalUser.getIdentifier())).thenReturn(false);
+ when(mMockUserManager.isManagedProfile(
+ mOtherUser1.getIdentifier())).thenReturn(false);
+ when(mMockUserManager.isManagedProfile(
+ mOtherUser2.getIdentifier())).thenReturn(false);
+ when(mMockUserManager.isManagedProfile(
+ mOtherUser3.getIdentifier())).thenReturn(false);
+
+ // set all user profile parents
+ when(mMockUserManager.getProfileParent(mPersonalUser)).thenReturn(null);
+ when(mMockUserManager.getProfileParent(mManagedUser)).thenReturn(mPersonalUser);
+ when(mMockUserManager.getProfileParent(mOtherUser1)).thenReturn(mPersonalUser);
+ when(mMockUserManager.getProfileParent(mOtherUser2)).thenReturn(mPersonalUser);
+ when(mMockUserManager.getProfileParent(mOtherUser3)).thenReturn(mPersonalUser);
+
+ if (SdkLevel.isAtLeastV()) {
+ //Personal user
+ UserProperties mPersonalUserProperties = new UserProperties.Builder().build();
+
+ UserProperties mManagedUserProperties = new UserProperties.Builder() // managed user
+ .setShowInSharingSurfaces(UserProperties.SHOW_IN_SHARING_SURFACES_SEPARATE)
+ .setCrossProfileContentSharingStrategy(
+ UserProperties.CROSS_PROFILE_CONTENT_SHARING_NO_DELEGATION)
+ .setShowInQuietMode(UserProperties.SHOW_IN_QUIET_MODE_PAUSED)
+ .build();
+
+ UserProperties mOtherUser1Properties = new UserProperties.Builder() // private user
+ .setShowInSharingSurfaces(UserProperties.SHOW_IN_SHARING_SURFACES_SEPARATE)
+ .setCrossProfileContentSharingStrategy(
+ UserProperties.CROSS_PROFILE_CONTENT_SHARING_DELEGATE_FROM_PARENT)
+ .setShowInQuietMode(UserProperties.SHOW_IN_QUIET_MODE_HIDDEN)
+ .build();
+
+ UserProperties mOtherUser2Properties = new UserProperties.Builder() // clone user
+ .setShowInSharingSurfaces(UserProperties.SHOW_IN_SHARING_SURFACES_WITH_PARENT)
+ .setCrossProfileContentSharingStrategy(
+ UserProperties.CROSS_PROFILE_CONTENT_SHARING_DELEGATE_FROM_PARENT)
+ .setShowInQuietMode(UserProperties.SHOW_IN_QUIET_MODE_DEFAULT)
+ .build();
+
+ // get user properties
+ when(mMockUserManager.getUserProperties(mPersonalUser))
+ .thenReturn(mPersonalUserProperties);
+ when(mMockUserManager.getUserProperties(mManagedUser))
+ .thenReturn(mManagedUserProperties);
+ when(mMockUserManager.getUserProperties(mOtherUser1)).thenReturn(mOtherUser1Properties);
+ when(mMockUserManager.getUserProperties(mOtherUser2)).thenReturn(mOtherUser2Properties);
+ }
+
+ when(mMockContext.getSystemServiceName(UserManager.class)).thenReturn(
+ "mockUserManager");
+ when(mMockContext.getSystemService(UserManager.class)).thenReturn(mMockUserManager);
+ when(mMockContext.getPackageManager()).thenReturn(mMockPackageManager);
+ }
+
+ @Test
+ public void testUserManagerStateThrowsErrorIfCalledFromNonMainThread() {
+ initializeUserManagerState(UserId.of(mPersonalUser),
+ Arrays.asList(mPersonalUser, mManagedUser, mOtherUser1, mOtherUser2));
+ assertThrows(IllegalStateException.class, () -> mUserManagerState.isMultiUserProfiles());
+ assertThrows(IllegalStateException.class,
+ () -> mUserManagerState.isManagedUserProfile(UserId.of(mManagedUser)));
+ assertThrows(IllegalStateException.class,
+ () -> mUserManagerState.getCurrentUserProfileId());
+ assertThrows(IllegalStateException.class,
+ () -> mUserManagerState.getCrossProfileAllowedStatusForAll());
+ assertThrows(IllegalStateException.class, () -> mUserManagerState.getAllUserProfileIds());
+ assertThrows(IllegalStateException.class,
+ () -> mUserManagerState.updateProfileOffValuesAndPostCrossProfileStatus());
+ assertThrows(IllegalStateException.class,
+ () -> mUserManagerState.waitForMediaProviderToBeAvailable(
+ UserId.of(mPersonalUser)));
+ assertThrows(IllegalStateException.class,
+ () -> mUserManagerState.isCrossProfileAllowedToUser(UserId.of(mManagedUser)));
+ assertThrows(IllegalStateException.class, () -> mUserManagerState.resetUserIds());
+ assertThrows(IllegalStateException.class, () -> mUserManagerState.isCurrentUserSelected());
+ assertThrows(IllegalStateException.class,
+ () -> mUserManagerState.isBlockedByAdmin(UserId.of(mManagedUser)));
+ assertThrows(IllegalStateException.class,
+ () -> mUserManagerState.isProfileOff(UserId.of(mManagedUser)));
+ assertThrows(IllegalStateException.class,
+ () -> mUserManagerState.getShowInQuietMode(UserId.of(mOtherUser1)));
+ assertThrows(IllegalStateException.class,
+ () -> mUserManagerState.setUserAsCurrentUserProfile(UserId.of(mManagedUser)));
+ assertThrows(IllegalStateException.class,
+ () -> mUserManagerState.isUserSelectedAsCurrentUserProfile(
+ UserId.of(mPersonalUser)));
+ }
+
+ @Test
+ public void testGetAllUserProfileIdsThatNeedToShowInPhotoPicker_currentUserIsPersonalUser() {
+ initializeUserManagerState(UserId.of(mPersonalUser),
+ Arrays.asList(mPersonalUser, mManagedUser, mOtherUser1, mOtherUser2));
+ InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
+
+ List<UserId> userIdList = SdkLevel.isAtLeastV() ? Arrays.asList(
+ UserId.of(mPersonalUser), UserId.of(mManagedUser), UserId.of(mOtherUser1))
+ : Arrays.asList(UserId.of(mPersonalUser), UserId.of(mManagedUser));
+
+ assertThat(mUserManagerState.getAllUserProfileIds())
+ .containsExactlyElementsIn(userIdList);
+ });
+ }
+
+ @Test
+ public void testGetAllUserProfileIdsThatNeedToShowInPhotoPicker_currentUserIsManagedUser() {
+ initializeUserManagerState(UserId.of(mManagedUser),
+ Arrays.asList(mPersonalUser, mManagedUser, mOtherUser1, mOtherUser2));
+ InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
+ List<UserId> userIdList = SdkLevel.isAtLeastV() ? Arrays.asList(
+ UserId.of(mPersonalUser), UserId.of(mManagedUser), UserId.of(mOtherUser1))
+ : Arrays.asList(UserId.of(mPersonalUser), UserId.of(mManagedUser));
+
+ assertThat(mUserManagerState.getAllUserProfileIds())
+ .containsExactlyElementsIn(userIdList);
+ });
+ }
+ @Test
+ public void testGetAllUserProfileIdsThatNeedToShowInPhotoPicker_currentUserIsOtherUser1() {
+ initializeUserManagerState(UserId.of(mOtherUser1),
+ Arrays.asList(mPersonalUser, mManagedUser, mOtherUser1, mOtherUser2));
+ InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
+
+ List<UserId> userIdList = SdkLevel.isAtLeastV() ? Arrays.asList(
+ UserId.of(mPersonalUser), UserId.of(mManagedUser), UserId.of(mOtherUser1))
+ : Arrays.asList(UserId.of(mPersonalUser), UserId.of(mManagedUser));
+ assertThat(mUserManagerState.getAllUserProfileIds())
+ .containsExactlyElementsIn(userIdList);
+ });
+ }
+
+ @Test
+ public void testUserIds_singleUserProfileAvailable() {
+ // if current user is personal and no other profile is available
+ UserId currentUser = UserId.of(mPersonalUser);
+ initializeUserManagerState(currentUser, Arrays.asList(mPersonalUser));
+ InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
+ assertThat(mUserManagerState.isMultiUserProfiles()).isFalse();
+
+ assertThat(mUserManagerState.getCurrentUserProfileId())
+ .isEqualTo(UserId.of(mPersonalUser));
+ assertThat(mUserManagerState.getAllUserProfileIds())
+ .containsExactlyElementsIn(Arrays.asList(UserId.of(mPersonalUser)));
+ });
+ }
+
+
+ @Test
+ public void testUserIds_multiUserProfilesAvailable_currentUserIsPersonalUser() {
+ UserId currentUser = UserId.of(mPersonalUser);
+
+ // if available user profiles are {personal , managed, otherUser1 }
+ initializeUserManagerState(currentUser,
+ Arrays.asList(mPersonalUser, mManagedUser, mOtherUser1));
+ InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
+ assertThat(mUserManagerState.isMultiUserProfiles()).isTrue();
+
+ assertThat(mUserManagerState.getCurrentUserProfileId()).isEqualTo(UserId.of(
+ mPersonalUser));
+
+ List<UserId> userIdList = SdkLevel.isAtLeastV() ? Arrays.asList(
+ UserId.of(mPersonalUser), UserId.of(mManagedUser), UserId.of(mOtherUser1))
+ : Arrays.asList(UserId.of(mPersonalUser), UserId.of(mManagedUser));
+ assertThat(mUserManagerState.getAllUserProfileIds())
+ .containsExactlyElementsIn(userIdList);
+
+ assertThat(mUserManagerState.isManagedUserProfile(
+ mUserManagerState.getCurrentUserProfileId())).isFalse();
+ });
+
+ // if available user profiles are {personal , otherUser1 }
+ initializeUserManagerState(currentUser, Arrays.asList(mPersonalUser, mOtherUser1));
+ InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
+
+ if (SdkLevel.isAtLeastV()) {
+ assertThat(mUserManagerState.isMultiUserProfiles()).isTrue();
+ } else {
+ assertThat(mUserManagerState.isMultiUserProfiles()).isFalse();
+ }
+
+ List<UserId> userIdList = SdkLevel.isAtLeastV() ? Arrays.asList(
+ UserId.of(mPersonalUser), UserId.of(mOtherUser1))
+ : Arrays.asList(UserId.of(mPersonalUser));
+ assertThat(mUserManagerState.getAllUserProfileIds())
+ .containsExactlyElementsIn(userIdList);
+ });
+
+
+ // if available user profiles are {personal , otherUser2 }
+ initializeUserManagerState(currentUser, Arrays.asList(mPersonalUser, mOtherUser2));
+ InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
+ assertThat(mUserManagerState.isMultiUserProfiles()).isFalse();
+
+ assertThat(mUserManagerState.getCurrentUserProfileId())
+ .isEqualTo(UserId.of(mPersonalUser));
+ assertThat(mUserManagerState.getAllUserProfileIds())
+ .containsExactlyElementsIn(Arrays.asList(UserId.of(mPersonalUser)));
+ });
+ }
+
+ @Test
+ public void testUserIds_multiUserProfilesAvailable_currentUserIsOtherUser2() {
+ UserId currentUser = UserId.of(mOtherUser2);
+
+ initializeUserManagerState(currentUser,
+ Arrays.asList(mPersonalUser, mManagedUser, mOtherUser1, mOtherUser2));
+ InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
+ assertThat(mUserManagerState.isMultiUserProfiles()).isTrue();
+ assertThat(mUserManagerState.getCurrentUserProfileId())
+ .isEqualTo(UserId.of(mOtherUser2));
+
+ List<UserId> userIdList = SdkLevel.isAtLeastV() ? Arrays.asList(
+ UserId.of(mPersonalUser), UserId.of(mManagedUser), UserId.of(mOtherUser1))
+ : Arrays.asList(UserId.of(mPersonalUser), UserId.of(mManagedUser));
+ assertThat(mUserManagerState.getAllUserProfileIds())
+ .containsExactlyElementsIn(userIdList);
+
+ });
+
+ initializeUserManagerState(currentUser, Arrays.asList(mPersonalUser, mOtherUser2));
+ InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
+ assertThat(mUserManagerState.isMultiUserProfiles()).isFalse();
+ assertThat(mUserManagerState.getAllUserProfileIds())
+ .containsExactlyElementsIn(Arrays.asList(UserId.of(mPersonalUser)));
+
+ });
+ }
+
+ @Test
+ public void testCurrentUser_AfterSettingASpecificUserAsCurrentUser() {
+ UserId currentUser = UserId.of(mPersonalUser);
+ initializeUserManagerState(currentUser,
+ Arrays.asList(mPersonalUser, mManagedUser, mOtherUser1, mOtherUser2));
+
+ // set current user as managed profile
+ InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
+ mUserManagerState.setUserAsCurrentUserProfile(UserId.of(mManagedUser));
+
+ assertThat(mUserManagerState.getCurrentUserProfileId())
+ .isEqualTo(UserId.of(mManagedUser));
+ assertThat(mUserManagerState.isManagedUserProfile(
+ mUserManagerState.getCurrentUserProfileId())).isTrue();
+ });
+
+ // set current user as otherUser2
+ InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
+ mUserManagerState.setUserAsCurrentUserProfile(UserId.of(mOtherUser2));
+ assertThat(mUserManagerState.getCurrentUserProfileId())
+ .isEqualTo(UserId.of(mManagedUser));
+ });
+
+ // set current user as otherUser1
+ InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
+ mUserManagerState.setUserAsCurrentUserProfile(UserId.of(mOtherUser1));
+ UserHandle currentUserProfile = SdkLevel.isAtLeastV() ? mOtherUser1 : mManagedUser;
+ assertThat(mUserManagerState.getCurrentUserProfileId())
+ .isEqualTo(UserId.of(currentUserProfile));
+ });
+
+ // set current user as personalUser
+ InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
+ mUserManagerState.setUserAsCurrentUserProfile(UserId.of(mPersonalUser));
+ assertThat(mUserManagerState.getCurrentUserProfileId())
+ .isEqualTo(UserId.of(mPersonalUser));
+ });
+
+ // set current user otherUser3
+ InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
+ mUserManagerState.setUserAsCurrentUserProfile(UserId.of(mOtherUser3));
+ assertThat(mUserManagerState.getCurrentUserProfileId())
+ .isEqualTo(UserId.of(mPersonalUser));
+
+ List<UserId> userIdList = SdkLevel.isAtLeastV() ? Arrays.asList(
+ UserId.of(mPersonalUser), UserId.of(mManagedUser), UserId.of(mOtherUser1))
+ : Arrays.asList(UserId.of(mPersonalUser), UserId.of(mManagedUser));
+ assertThat(mUserManagerState.getAllUserProfileIds())
+ .containsExactlyElementsIn(userIdList);
+ });
+
+ }
+
+
+
+ private void initializeUserManagerState(UserId current, List<UserHandle> usersOnDevice) {
+ when(mMockUserManager.getUserProfiles()).thenReturn(usersOnDevice);
+ InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
+ mUserManagerState = new UserManagerState.RuntimeUserManagerState(mMockContext, current);
+ });
+ }
+}
diff --git a/tests/src/com/android/providers/media/photopicker/espresso/AlbumsTabTest.java b/tests/src/com/android/providers/media/photopicker/espresso/AlbumsTabTest.java
index 5cc870d..b528d36 100644
--- a/tests/src/com/android/providers/media/photopicker/espresso/AlbumsTabTest.java
+++ b/tests/src/com/android/providers/media/photopicker/espresso/AlbumsTabTest.java
@@ -24,6 +24,7 @@
import static androidx.test.espresso.matcher.ViewMatchers.isDisplayed;
import static androidx.test.espresso.matcher.ViewMatchers.withContentDescription;
import static androidx.test.espresso.matcher.ViewMatchers.withId;
+import static androidx.test.espresso.matcher.ViewMatchers.withParent;
import static androidx.test.espresso.matcher.ViewMatchers.withText;
import static com.android.providers.media.photopicker.espresso.OverflowMenuUtils.assertOverflowMenuNotShown;
@@ -119,6 +120,10 @@
.check(matches(withText(albumNameResId)))
.perform(click());
+ // Verify album opened
+ onView(allOf(withText(albumNameResId), withParent(withId(R.id.toolbar))))
+ .check(matches(isDisplayed()));
+
// Verify album click UI event
UiEventLoggerTestUtils.verifyLogWithInstanceId(mRule, getUiEventForAlbumId(albumNameResId));
diff --git a/tests/src/com/android/providers/media/photopicker/espresso/PreviewMultiSelectTest.java b/tests/src/com/android/providers/media/photopicker/espresso/PreviewMultiSelectTest.java
index c6befda..64b91c3 100644
--- a/tests/src/com/android/providers/media/photopicker/espresso/PreviewMultiSelectTest.java
+++ b/tests/src/com/android/providers/media/photopicker/espresso/PreviewMultiSelectTest.java
@@ -61,7 +61,6 @@
import org.hamcrest.Description;
import org.hamcrest.Matcher;
import org.hamcrest.TypeSafeMatcher;
-import org.junit.Ignore;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -181,12 +180,10 @@
}
@Test
- @Ignore("Enable after b/218806007 is fixed")
public void testPreview_multiSelect_navigation() throws Exception {
onView(withId(PICKER_TAB_RECYCLERVIEW_ID)).check(matches(isDisplayed()));
// Select items
- clickItem(PICKER_TAB_RECYCLERVIEW_ID, VIDEO_POSITION, ICON_THUMBNAIL_ID);
clickItem(PICKER_TAB_RECYCLERVIEW_ID, IMAGE_2_POSITION, ICON_THUMBNAIL_ID);
clickItem(PICKER_TAB_RECYCLERVIEW_ID, IMAGE_1_POSITION, ICON_THUMBNAIL_ID);
// Navigate to preview
@@ -197,9 +194,7 @@
// Preview Order
// 1 - Image
// 2 - Image
- // 3 - Video
- // Navigate from Image -> Image -> Video -> Image -> Image -> Image and verify the
- // layout matches
+ // Navigate from Image -> Image -> Image -> Image and verify the layout matches
// 1. Image
assertMultiSelectPreviewCommonLayoutDisplayed();
@@ -216,25 +211,6 @@
// Verify no special format icon is previewed
assertSpecialFormatBadgeDoesNotExist();
- swipeLeftAndWait(PREVIEW_VIEW_PAGER_ID);
- // 3. Video item
- assertMultiSelectPreviewCommonLayoutDisplayed();
- // TODO(b/197083539): We don't check the video image to be visible or not because its
- // visibility is time sensitive. Try waiting till player is ready and assert that video
- // image is no more visible.
- onView(ViewPagerMatcher(PREVIEW_VIEW_PAGER_ID, VIDEO_PREVIEW_THUMBNAIL_ID))
- .check(matches(isDisplayed()));
- // Verify no special format icon is previewed
- assertSpecialFormatBadgeDoesNotExist();
-
- swipeRightAndWait(PREVIEW_VIEW_PAGER_ID);
- // 2. Image
- assertMultiSelectPreviewCommonLayoutDisplayed();
- onView(ViewPagerMatcher(PREVIEW_VIEW_PAGER_ID, PREVIEW_IMAGE_VIEW_ID))
- .check(matches(isDisplayed()));
- // Verify no special format icon is previewed
- assertSpecialFormatBadgeDoesNotExist();
-
swipeRightAndWait(PREVIEW_VIEW_PAGER_ID);
// 1. Image
assertMultiSelectPreviewCommonLayoutDisplayed();
diff --git a/tests/src/com/android/providers/media/photopicker/sync/PickerSyncManagerTest.java b/tests/src/com/android/providers/media/photopicker/sync/PickerSyncManagerTest.java
index ae7221c..b450e6c 100644
--- a/tests/src/com/android/providers/media/photopicker/sync/PickerSyncManagerTest.java
+++ b/tests/src/com/android/providers/media/photopicker/sync/PickerSyncManagerTest.java
@@ -27,6 +27,7 @@
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.clearInvocations;
import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.MockitoAnnotations.initMocks;
@@ -95,10 +96,28 @@
"com.hooli.super.awesome.cloudpicker");
}
+
+ @Test
+ public void testScheduleEndlessWorker() {
+ setupPickerSyncManager(/* schedulePeriodicSyncs */ false);
+
+ // The third call here comes from the EndlessWorker
+ verify(mMockWorkManager, times(1))
+ .enqueueUniqueWork(anyString(),
+ any(),
+ mOneTimeWorkRequestArgumentCaptor.capture());
+
+ final OneTimeWorkRequest workRequest = mOneTimeWorkRequestArgumentCaptor.getValue();
+ assertThat(workRequest.getWorkSpec().workerClassName)
+ .isEqualTo(EndlessWorker.class.getName());
+ assertThat(workRequest.getWorkSpec().expedited).isFalse();
+ }
+
@Test
public void testSchedulePeriodicSyncs() {
setupPickerSyncManager(/* schedulePeriodicSyncs */ true);
+ // The third call here comes from the EndlessWorker
verify(mMockWorkManager, times(2))
.enqueueUniquePeriodicWork(anyString(),
any(),
@@ -193,6 +212,7 @@
@Test
public void testAdhocProactiveSyncLocalOnly() {
setupPickerSyncManager(/* schedulePeriodicSyncs */ false);
+ reset(mMockWorkManager);
mPickerSyncManager.syncMediaProactively(/* localOnly */ true);
verify(mMockWorkManager, times(1))
@@ -216,6 +236,8 @@
public void testAdhocProactiveSync() {
setupPickerSyncManager(/* schedulePeriodicSyncs */ false);
+ reset(mMockWorkManager);
+
mPickerSyncManager.syncMediaProactively(/* localOnly */ false);
verify(mMockWorkManager, times(1))
.enqueueUniqueWork(anyString(),
@@ -238,6 +260,7 @@
public void testImmediateLocalSync() {
setupPickerSyncManager(/* schedulePeriodicSyncs */ false);
+ reset(mMockWorkManager);
mPickerSyncManager.syncMediaImmediately(true);
verify(mMockWorkManager, times(1))
.enqueueUniqueWork(anyString(), any(), mOneTimeWorkRequestArgumentCaptor.capture());
@@ -258,6 +281,8 @@
public void testImmediateCloudSync() {
setupPickerSyncManager(/* schedulePeriodicSyncs */ false);
+ reset(mMockWorkManager);
+
mPickerSyncManager.syncMediaImmediately(false);
verify(mMockWorkManager, times(2))
.enqueueUniqueWork(anyString(), any(), mOneTimeWorkRequestArgumentCaptor.capture());
@@ -294,7 +319,8 @@
setupPickerSyncManager(/* schedulePeriodicSyncs */ false);
mPickerSyncManager.syncAlbumMediaForProviderImmediately(
- "Not_null", PickerSyncController.LOCAL_PICKER_PROVIDER_AUTHORITY);
+ "Not_null", PickerSyncController.LOCAL_PICKER_PROVIDER_AUTHORITY,
+ /* isLocal= */ true);
verify(mMockWorkManager, times(1))
.beginUniqueWork(
anyString(),
@@ -332,7 +358,7 @@
setupPickerSyncManager(/* schedulePeriodicSyncs */ false);
mPickerSyncManager.syncAlbumMediaForProviderImmediately(
- "Not_null", "com.hooli.cloudpicker");
+ "Not_null", "com.hooli.cloudpicker", /* isLocal= */ false);
verify(mMockWorkManager, times(1))
.beginUniqueWork(
anyString(),
diff --git a/tests/src/com/android/providers/media/photopicker/viewmodel/BannerTestUtils.java b/tests/src/com/android/providers/media/photopicker/viewmodel/BannerTestUtils.java
index 4a8badf..4012acc 100644
--- a/tests/src/com/android/providers/media/photopicker/viewmodel/BannerTestUtils.java
+++ b/tests/src/com/android/providers/media/photopicker/viewmodel/BannerTestUtils.java
@@ -23,6 +23,7 @@
import com.android.providers.media.ConfigStore;
import com.android.providers.media.photopicker.data.UserIdManager;
+import com.android.providers.media.photopicker.data.UserManagerState;
class BannerTestUtils {
static BannerController getTestBannerController(@NonNull Context context,
@@ -70,4 +71,40 @@
}
};
}
+
+ static BannerManager getTestCloudBannerManager(@NonNull Context context,
+ @NonNull UserManagerState userManagerState, @NonNull ConfigStore configStore) {
+ return new BannerManager.CloudBannerManager(context, userManagerState, configStore) {
+ @Override
+ void maybeInitialiseAndSetBannersForCurrentUser() {
+ // Get (iff exists) or create the banner controller for the current user
+ final BannerController bannerController =
+ getBannerControllersPerUser().forUser(getCurrentUserProfileId());
+ // Post the banner related live data values from this current user banner controller
+ getCloudMediaProviderAuthorityLiveData()
+ .postValue(bannerController.getCloudMediaProviderAuthority());
+ getCloudMediaProviderAppTitleLiveData()
+ .postValue(bannerController.getCloudMediaProviderLabel());
+ getCloudMediaAccountNameLiveData()
+ .postValue(bannerController.getCloudMediaProviderAccountName());
+ setChooseCloudMediaAccountActivityIntent(
+ bannerController.getChooseCloudMediaAccountActivityIntent());
+ shouldShowChooseAppBannerLiveData()
+ .postValue(bannerController.shouldShowChooseAppBanner());
+ shouldShowCloudMediaAvailableBannerLiveData()
+ .postValue(bannerController.shouldShowCloudMediaAvailableBanner());
+ shouldShowAccountUpdatedBannerLiveData()
+ .postValue(bannerController.shouldShowAccountUpdatedBanner());
+ shouldShowChooseAccountBannerLiveData()
+ .postValue(bannerController.shouldShowChooseAccountBanner());
+ }
+
+ @NonNull
+ @Override
+ BannerController createBannerController(@NonNull Context context,
+ @NonNull UserHandle userHandle, @NonNull ConfigStore configStore) {
+ return getTestBannerController(context, userHandle, configStore);
+ }
+ };
+ }
}
diff --git a/tests/src/com/android/providers/media/photopicker/viewmodel/PickerViewModelPaginationTest.java b/tests/src/com/android/providers/media/photopicker/viewmodel/PickerViewModelPaginationTest.java
index 9e1ec53..8bb2500 100644
--- a/tests/src/com/android/providers/media/photopicker/viewmodel/PickerViewModelPaginationTest.java
+++ b/tests/src/com/android/providers/media/photopicker/viewmodel/PickerViewModelPaginationTest.java
@@ -47,22 +47,26 @@
import androidx.lifecycle.LiveData;
import androidx.test.filters.SdkSuppress;
-import androidx.test.runner.AndroidJUnit4;
+import com.android.modules.utils.build.SdkLevel;
import com.android.providers.media.IsolatedContext;
import com.android.providers.media.TestConfigStore;
import com.android.providers.media.photopicker.DataLoaderThread;
import com.android.providers.media.photopicker.data.ItemsProvider;
import com.android.providers.media.photopicker.data.PaginationParameters;
import com.android.providers.media.photopicker.data.UserIdManager;
+import com.android.providers.media.photopicker.data.UserManagerState;
import com.android.providers.media.photopicker.data.model.Category;
import com.android.providers.media.photopicker.data.model.Item;
import com.android.providers.media.photopicker.data.model.UserId;
+import com.google.android.collect.Lists;
+
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
@@ -72,7 +76,7 @@
import java.util.ArrayList;
import java.util.List;
-@RunWith(AndroidJUnit4.class)
+@RunWith(Parameterized.class)
public class PickerViewModelPaginationTest {
@Rule
@@ -88,6 +92,18 @@
private static final String TAG = "PickerViewModelTest";
private ContentResolver mIsolatedResolver;
+ @Parameterized.Parameter(0)
+ public boolean isPrivateSpaceEnabled;
+
+ /**
+ * Parametrize values for {@code isPrivateSpaceEnabled} to run all the tests twice once with
+ * private space flag enabled and once with it disabled.
+ */
+ @Parameterized.Parameters(name = "privateSpaceEnabled={0}")
+ public static Iterable<?> data() {
+ return Lists.newArrayList(true, false);
+ }
+
public PickerViewModelPaginationTest() {
@@ -104,6 +120,11 @@
final TestConfigStore testConfigStore = new TestConfigStore();
testConfigStore.enableCloudMediaFeature();
+ if (isPrivateSpaceEnabled) {
+ testConfigStore.enablePrivateSpaceInPhotoPicker();
+ } else {
+ testConfigStore.disablePrivateSpaceInPhotoPicker();
+ }
final Context isolatedContext = new IsolatedContext(sTargetContext, /* tag */ "databases",
/* asFuseThread */ false, sTargetContext.getUser(), testConfigStore);
@@ -116,9 +137,16 @@
}
};
});
- final UserIdManager userIdManager = mock(UserIdManager.class);
- when(userIdManager.getCurrentUserProfileId()).thenReturn(UserId.CURRENT_USER);
- mPickerViewModel.setUserIdManager(userIdManager);
+ if (testConfigStore.isPrivateSpaceInPhotoPickerEnabled() && SdkLevel.isAtLeastS()) {
+ final UserManagerState userManagerState = mock(UserManagerState.class);
+ when(userManagerState.getCurrentUserProfileId()).thenReturn(UserId.CURRENT_USER);
+ mPickerViewModel.setUserManagerState(userManagerState);
+ } else {
+ final UserIdManager userIdManager = mock(UserIdManager.class);
+ when(userIdManager.getCurrentUserProfileId()).thenReturn(UserId.CURRENT_USER);
+ mPickerViewModel.setUserIdManager(userIdManager);
+ }
+
mIsolatedResolver = isolatedContext.getContentResolver();
final ItemsProvider itemsProvider = new ItemsProvider(isolatedContext);
mPickerViewModel.setItemsProvider(itemsProvider);
diff --git a/tests/src/com/android/providers/media/photopicker/viewmodel/PickerViewModelTest.java b/tests/src/com/android/providers/media/photopicker/viewmodel/PickerViewModelTest.java
index f9f9351..07f9954 100644
--- a/tests/src/com/android/providers/media/photopicker/viewmodel/PickerViewModelTest.java
+++ b/tests/src/com/android/providers/media/photopicker/viewmodel/PickerViewModelTest.java
@@ -64,8 +64,8 @@
import androidx.annotation.Nullable;
import androidx.lifecycle.LiveData;
import androidx.test.filters.SdkSuppress;
-import androidx.test.runner.AndroidJUnit4;
+import com.android.modules.utils.build.SdkLevel;
import com.android.providers.media.TestConfigStore;
import com.android.providers.media.photopicker.DataLoaderThread;
import com.android.providers.media.photopicker.PickerSyncController;
@@ -73,15 +73,20 @@
import com.android.providers.media.photopicker.data.PaginationParameters;
import com.android.providers.media.photopicker.data.Selection;
import com.android.providers.media.photopicker.data.UserIdManager;
+import com.android.providers.media.photopicker.data.UserManagerState;
import com.android.providers.media.photopicker.data.model.Category;
import com.android.providers.media.photopicker.data.model.Item;
import com.android.providers.media.photopicker.data.model.ModelTestUtils;
+import com.android.providers.media.photopicker.data.model.RefreshRequest;
import com.android.providers.media.photopicker.data.model.UserId;
+import com.google.android.collect.Lists;
+
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
@@ -91,7 +96,7 @@
import java.util.Objects;
import java.util.concurrent.TimeUnit;
-@RunWith(AndroidJUnit4.class)
+@RunWith(Parameterized.class)
public class PickerViewModelTest {
private static final String FAKE_CATEGORY_NAME = "testCategoryName";
private static final String FAKE_ID = "5";
@@ -111,6 +116,17 @@
private TestConfigStore mConfigStore;
private BannerManager mBannerManager;
private BannerController mBannerController;
+ @Parameterized.Parameter(0)
+ public boolean isPrivateSpaceEnabled;
+
+ /**
+ * Parametrize values for {@code isPrivateSpaceEnabled} to run all the tests twice once with
+ * private space flag enabled and once with it disabled.
+ */
+ @Parameterized.Parameters(name = "privateSpaceEnabled={0}")
+ public static Iterable<?> data() {
+ return Lists.newArrayList(true, false);
+ }
public PickerViewModelTest() {
}
@@ -123,6 +139,11 @@
mConfigStore = new TestConfigStore();
mConfigStore.enableCloudMediaFeatureAndSetAllowedCloudProviderPackages(TEST_PACKAGE_NAME);
mConfigStore.enablePickerChoiceManagedSelectionEnabled();
+ if (isPrivateSpaceEnabled) {
+ mConfigStore.enablePrivateSpaceInPhotoPicker();
+ } else {
+ mConfigStore.disablePrivateSpaceInPhotoPicker();
+ }
getInstrumentation().runOnMainSync(() -> {
mPickerViewModel = new PickerViewModel(mApplication) {
@@ -134,12 +155,22 @@
});
mItemsProvider = new TestItemsProvider(sTargetContext);
mPickerViewModel.setItemsProvider(mItemsProvider);
- final UserIdManager userIdManager = mock(UserIdManager.class);
- when(userIdManager.getCurrentUserProfileId()).thenReturn(UserId.CURRENT_USER);
- mPickerViewModel.setUserIdManager(userIdManager);
- mBannerManager = BannerTestUtils.getTestCloudBannerManager(sTargetContext, userIdManager,
- mConfigStore);
+ // set current user profile and banner manager
+ if (mConfigStore.isPrivateSpaceInPhotoPickerEnabled() && SdkLevel.isAtLeastS()) {
+ final UserManagerState userManagerState = mock(UserManagerState.class);
+ when(userManagerState.getCurrentUserProfileId()).thenReturn(UserId.CURRENT_USER);
+ mPickerViewModel.setUserManagerState(userManagerState);
+ mBannerManager = BannerTestUtils.getTestCloudBannerManager(
+ sTargetContext, userManagerState, mConfigStore);
+ } else {
+ final UserIdManager userIdManager = mock(UserIdManager.class);
+ when(userIdManager.getCurrentUserProfileId()).thenReturn(UserId.CURRENT_USER);
+ mPickerViewModel.setUserIdManager(userIdManager);
+ mBannerManager = BannerTestUtils.getTestCloudBannerManager(
+ sTargetContext, userIdManager, mConfigStore);
+ }
+
mPickerViewModel.setBannerManager(mBannerManager);
// Set default banner manager values
@@ -665,17 +696,17 @@
@Test
public void testRefreshUiNotifications() throws InterruptedException {
- final LiveData<Boolean> shouldRefreshUi = mPickerViewModel.shouldRefreshUiLiveData();
- assertFalse(shouldRefreshUi.getValue());
+ final LiveData<RefreshRequest> shouldRefreshUi = mPickerViewModel.refreshUiLiveData();
+ assertFalse(shouldRefreshUi.getValue().shouldRefreshPicker());
final ContentResolver contentResolver = sTargetContext.getContentResolver();
contentResolver.notifyChange(REFRESH_UI_PICKER_INTERNAL_OBSERVABLE_URI, null);
TimeUnit.MILLISECONDS.sleep(100);
- assertTrue(shouldRefreshUi.getValue());
+ assertTrue(shouldRefreshUi.getValue().shouldRefreshPicker());
- mPickerViewModel.resetAllContentInCurrentProfile();
- assertFalse(shouldRefreshUi.getValue());
+ mPickerViewModel.resetAllContentInCurrentProfile(false);
+ assertFalse(shouldRefreshUi.getValue().shouldRefreshPicker());
}
@Test
diff --git a/tests/src/com/android/providers/media/stableuris/job/StableUriIdleMaintenanceServiceTest.java b/tests/src/com/android/providers/media/stableuris/job/StableUriIdleMaintenanceServiceTest.java
index 3405ff0..0aee9df 100644
--- a/tests/src/com/android/providers/media/stableuris/job/StableUriIdleMaintenanceServiceTest.java
+++ b/tests/src/com/android/providers/media/stableuris/job/StableUriIdleMaintenanceServiceTest.java
@@ -18,6 +18,7 @@
import static com.android.providers.media.tests.utils.PublicVolumeSetupHelper.createNewPublicVolume;
import static com.android.providers.media.tests.utils.PublicVolumeSetupHelper.deletePublicVolumes;
+import static com.android.providers.media.tests.utils.PublicVolumeSetupHelper.executeShellCommand;
import static com.android.providers.media.util.FileUtils.getVolumePath;
import static org.junit.Assert.assertEquals;
@@ -33,6 +34,7 @@
import android.content.Context;
import android.database.Cursor;
import android.net.Uri;
+import android.os.Build;
import android.os.Environment;
import android.os.SystemClock;
import android.os.UserHandle;
@@ -45,6 +47,7 @@
import androidx.test.runner.AndroidJUnit4;
import com.android.providers.media.ConfigStore;
+import com.android.providers.media.DatabaseBackupAndRecovery;
import com.android.providers.media.stableuris.dao.BackupIdRow;
import org.junit.AfterClass;
@@ -63,7 +66,6 @@
import java.util.Set;
@RunWith(AndroidJUnit4.class)
-@SdkSuppress(minSdkVersion = 31, codeName = "S")
public class StableUriIdleMaintenanceServiceTest {
private static final String TAG = "StableUriIdleMaintenanceServiceTest";
@@ -86,6 +88,9 @@
@BeforeClass
public static void setUpClass() throws Exception {
adoptShellPermission();
+ if (Build.VERSION.SDK_INT == Build.VERSION_CODES.R) {
+ return;
+ }
// Read existing value of the flag
sInitialDeviceConfigValueForInternal = Boolean.parseBoolean(
@@ -110,6 +115,10 @@
@AfterClass
public static void tearDownClass() throws Exception {
+ if (Build.VERSION.SDK_INT == Build.VERSION_CODES.R) {
+ dropShellPermission();
+ return;
+ }
// Restore previous value of the flag
DeviceConfig.setProperty(ConfigStore.NAMESPACE_MEDIAPROVIDER,
@@ -126,6 +135,7 @@
}
@Test
+ @SdkSuppress(minSdkVersion = 31, codeName = "S")
public void testDataMigrationForInternalVolume() throws Exception {
final Context context = InstrumentationRegistry.getTargetContext();
final ContentResolver resolver = context.getContentResolver();
@@ -163,6 +173,11 @@
public void testDataMigrationForExternalVolume() throws Exception {
final Context context = InstrumentationRegistry.getTargetContext();
final ContentResolver resolver = context.getContentResolver();
+ // Enable feature for Android R
+ if (Build.VERSION.SDK_INT == Build.VERSION_CODES.R) {
+ executeShellCommand(
+ "setprop " + DatabaseBackupAndRecovery.STABLE_URI_EXTERNAL_PROPERTY + " true");
+ }
Set<String> newFilePaths = new HashSet<String>();
Map<String, Long> pathToIdMap = new HashMap<>();
MediaStore.waitForIdle(resolver);
@@ -207,6 +222,12 @@
for (String path : newFilePaths) {
new File(path).delete();
}
+
+ if (Build.VERSION.SDK_INT == Build.VERSION_CODES.R) {
+ executeShellCommand(
+ "setprop " + DatabaseBackupAndRecovery.STABLE_URI_EXTERNAL_PROPERTY
+ + " false");
+ }
}
}
diff --git a/tests/src/com/android/providers/media/util/MemoryTest.java b/tests/src/com/android/providers/media/util/MemoryTest.java
index ac86a93..aa9511b 100644
--- a/tests/src/com/android/providers/media/util/MemoryTest.java
+++ b/tests/src/com/android/providers/media/util/MemoryTest.java
@@ -30,11 +30,6 @@
private final byte[] buf = new byte[4];
@Test
- public void testConstructor() {
- new Memory();
- }
-
- @Test
public void testBigEndian() {
final int expected = 42;
Memory.pokeInt(buf, 0, expected, ByteOrder.BIG_ENDIAN);
diff --git a/tests/src/com/android/providers/media/util/MetricsTest.java b/tests/src/com/android/providers/media/util/MetricsTest.java
index 264cb1b..0feb420 100644
--- a/tests/src/com/android/providers/media/util/MetricsTest.java
+++ b/tests/src/com/android/providers/media/util/MetricsTest.java
@@ -27,10 +27,6 @@
@RunWith(AndroidJUnit4.class)
public class MetricsTest {
- @Test
- public void testConstructor() {
- new Metrics();
- }
/**
* The best we can do for coverage is make sure we don't explode?
diff --git a/tests/src/com/android/providers/media/util/PermissionUtilsTest.java b/tests/src/com/android/providers/media/util/PermissionUtilsTest.java
index 661fd62..c78ade8 100644
--- a/tests/src/com/android/providers/media/util/PermissionUtilsTest.java
+++ b/tests/src/com/android/providers/media/util/PermissionUtilsTest.java
@@ -50,6 +50,7 @@
import static com.android.providers.media.util.PermissionUtils.checkPermissionReadVisualUserSelected;
import static com.android.providers.media.util.PermissionUtils.checkPermissionSelf;
import static com.android.providers.media.util.PermissionUtils.checkPermissionShell;
+import static com.android.providers.media.util.PermissionUtils.checkPermissionSystem;
import static com.android.providers.media.util.PermissionUtils.checkPermissionWriteAudio;
import static com.android.providers.media.util.PermissionUtils.checkPermissionWriteImages;
import static com.android.providers.media.util.PermissionUtils.checkPermissionWriteStorage;
@@ -66,6 +67,7 @@
import android.content.Context;
import android.content.pm.PackageManager;
import android.os.Build;
+import android.os.Process;
import android.os.SystemClock;
import androidx.test.filters.SdkSuppress;
@@ -165,7 +167,7 @@
assertThat(checkPermissionShell(testAppUid)).isFalse();
assertThat(
checkIsLegacyStorageGranted(getContext(), testAppUid, packageName,
- null)).isFalse();
+ null, /* isTargetSdkAtLeastR */ true)).isFalse();
assertThat(
checkPermissionInstallPackages(getContext(), TEST_APP_PID, testAppUid,
packageName, null)).isFalse();
@@ -199,8 +201,8 @@
try {
assertThat(checkPermissionSelf(getContext(), TEST_APP_PID, testAppUid)).isFalse();
assertThat(checkPermissionShell(testAppUid)).isFalse();
- assertThat(checkIsLegacyStorageGranted(getContext(), testAppUid, packageName, null))
- .isFalse();
+ assertThat(checkIsLegacyStorageGranted(getContext(), testAppUid, packageName,
+ null, /* isTargetSdkAtLeastR */ true)).isFalse();
assertThat(checkPermissionInstallPackages(
getContext(), TEST_APP_PID, testAppUid, packageName, null)).isFalse();
assertThat(checkPermissionAccessMtp(
@@ -242,7 +244,7 @@
assertThat(
checkIsLegacyStorageGranted(getContext(), testAppUid, packageName,
- null)).isFalse();
+ null, /* isTargetSdkAtLeastR */ true)).isFalse();
assertThat(
checkPermissionInstallPackages(getContext(), TEST_APP_PID, testAppUid,
packageName, null)).isFalse();
@@ -291,7 +293,7 @@
assertThat(
checkIsLegacyStorageGranted(getContext(), testAppUid, packageName,
- null)).isTrue();
+ null, /* isTargetSdkAtLeastR */ false)).isTrue();
assertThat(
checkPermissionInstallPackages(getContext(), TEST_APP_PID, testAppUid,
packageName, null)).isFalse();
@@ -636,6 +638,15 @@
}
}
+ @Test
+ public void testSystemPermission() {
+ // false cases
+ assertThat(checkPermissionSystem(Process.ROOT_UID)).isFalse();
+ assertThat(checkPermissionSystem(Process.SHELL_UID)).isFalse();
+ // true cases
+ assertThat(checkPermissionSystem(Process.SYSTEM_UID)).isTrue();
+ }
+
static private void modifyAppOp(int uid, String op, int mode) {
getContext().getSystemService(AppOpsManager.class).setUidMode(op, uid, mode);
}
diff --git a/tests/src/com/android/providers/media/util/SQLiteQueryBuilderTest.java b/tests/src/com/android/providers/media/util/SQLiteQueryBuilderTest.java
index 2a01d61..3cfe18c 100644
--- a/tests/src/com/android/providers/media/util/SQLiteQueryBuilderTest.java
+++ b/tests/src/com/android/providers/media/util/SQLiteQueryBuilderTest.java
@@ -35,6 +35,7 @@
import android.os.CancellationSignal;
import android.os.OperationCanceledException;
import android.provider.MediaStore;
+import android.util.ArrayMap;
import androidx.test.InstrumentationRegistry;
import androidx.test.runner.AndroidJUnit4;
@@ -46,11 +47,14 @@
import org.junit.Test;
import org.junit.runner.RunWith;
+import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
+import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.Semaphore;
+import java.util.regex.Pattern;
@RunWith(AndroidJUnit4.class)
public class SQLiteQueryBuilderTest {
@@ -87,6 +91,40 @@
}
@Test
+ public void testCopyConstructor() {
+ SQLiteQueryBuilder qb = new SQLiteQueryBuilder();
+
+ ArrayMap<String, String> projectionMap = new ArrayMap<>();
+ projectionMap.put("EmployeeName", "name");
+ qb.setProjectionMap(projectionMap);
+
+ List<Pattern> projectionAllowList = new ArrayList<>();
+ projectionAllowList.add(Pattern.compile("abc"));
+ qb.setProjectionAllowlist(projectionAllowList);
+
+ qb.setStrictColumns(true);
+ qb.setStrict(true);
+ qb.setStrictGrammar(true);
+ qb.setTables(TEST_TABLE_NAME);
+ qb.setDistinct(true);
+
+ SQLiteQueryBuilder copy = new SQLiteQueryBuilder(qb);
+
+ assertThat(copy).isNotNull();
+ assertThat(copy).isNotSameInstanceAs(qb);
+ assertThat(copy.getProjectionMap()).isNotSameInstanceAs(qb.getProjectionMap());
+ assertThat(copy.getProjectionMap()).containsExactlyEntriesIn(qb.getProjectionMap());
+ assertThat(copy.getProjectionAllowlist()).isNotSameInstanceAs(qb.getProjectionAllowlist());
+ assertThat(copy.getProjectionAllowlist()).containsExactlyElementsIn(
+ qb.getProjectionAllowlist());
+ assertThat(copy.isStrictColumns()).isEqualTo(qb.isStrictColumns());
+ assertThat(copy.isStrict()).isEqualTo(qb.isStrict());
+ assertThat(copy.isStrictGrammar()).isEqualTo(qb.isStrictGrammar());
+ assertThat(copy.getTables()).isEqualTo(qb.getTables());
+ assertThat(copy.isDistinct()).isEqualTo(qb.isDistinct());
+ }
+
+ @Test
public void testSetDistinct() {
String expected;
SQLiteQueryBuilder sqliteQueryBuilder = new SQLiteQueryBuilder();
diff --git a/tools/ktfmt b/tools/ktfmt
new file mode 100644
index 0000000..eafad19
--- /dev/null
+++ b/tools/ktfmt
@@ -0,0 +1,5 @@
+ #!/usr/bin/env bash
+
+ROOT=$(dirname "$(realpath "$0")")/../../../../
+
+"$ROOT/prebuilts/jdk/jdk17/linux-x86/bin/java" -jar "$ROOT/prebuilts/build-tools/common/framework/ktfmt.jar" "$@"
diff --git a/tools/photopicker/res/values-af/strings.xml b/tools/photopicker/res/values-af/strings.xml
new file mode 100644
index 0000000..12f71c6
--- /dev/null
+++ b/tools/photopicker/res/values-af/strings.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Copyright (C) 2023 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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="picker_launch_tab_option" msgid="9190702440227174597">"STEL OORTJIE OM KIESER TE LAANSEER"</string>
+</resources>
diff --git a/tools/photopicker/res/values-am/strings.xml b/tools/photopicker/res/values-am/strings.xml
new file mode 100644
index 0000000..d4baa3a
--- /dev/null
+++ b/tools/photopicker/res/values-am/strings.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Copyright (C) 2023 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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="picker_launch_tab_option" msgid="9190702440227174597">"ፒከር አስጀማሪ ትር አዘጋጅ"</string>
+</resources>
diff --git a/tools/photopicker/res/values-ar/strings.xml b/tools/photopicker/res/values-ar/strings.xml
new file mode 100644
index 0000000..2f7a95f
--- /dev/null
+++ b/tools/photopicker/res/values-ar/strings.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Copyright (C) 2023 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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="picker_launch_tab_option" msgid="9190702440227174597">"ضبط علامة التبويب الخاصة بتفعيل أداة الاختيار"</string>
+</resources>
diff --git a/tools/photopicker/res/values-as/strings.xml b/tools/photopicker/res/values-as/strings.xml
new file mode 100644
index 0000000..8ea1c7a
--- /dev/null
+++ b/tools/photopicker/res/values-as/strings.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Copyright (C) 2023 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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="picker_launch_tab_option" msgid="9190702440227174597">"বাছনিকৰ্তা লঞ্চ কৰা টেব ছেট কৰক"</string>
+</resources>
diff --git a/tools/photopicker/res/values-az/strings.xml b/tools/photopicker/res/values-az/strings.xml
new file mode 100644
index 0000000..ee88375
--- /dev/null
+++ b/tools/photopicker/res/values-az/strings.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Copyright (C) 2023 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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="picker_launch_tab_option" msgid="9190702440227174597">"SEÇİCİ BAŞLATMA TABINI SEÇİN"</string>
+</resources>
diff --git a/tools/photopicker/res/values-b+sr+Latn/strings.xml b/tools/photopicker/res/values-b+sr+Latn/strings.xml
new file mode 100644
index 0000000..3498a06
--- /dev/null
+++ b/tools/photopicker/res/values-b+sr+Latn/strings.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Copyright (C) 2023 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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="picker_launch_tab_option" msgid="9190702440227174597">"PODEŠAVANJE KARTICE ZA POKRETANJE BIRAČA"</string>
+</resources>
diff --git a/tools/photopicker/res/values-be/strings.xml b/tools/photopicker/res/values-be/strings.xml
new file mode 100644
index 0000000..a862bbe
--- /dev/null
+++ b/tools/photopicker/res/values-be/strings.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Copyright (C) 2023 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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="picker_launch_tab_option" msgid="9190702440227174597">"ЗАДАЦЬ УКЛАДКУ ДЛЯ ЗАПУСКУ ІНСТРУМЕНТА ВЫБАРУ"</string>
+</resources>
diff --git a/tools/photopicker/res/values-bg/strings.xml b/tools/photopicker/res/values-bg/strings.xml
new file mode 100644
index 0000000..de3bf69
--- /dev/null
+++ b/tools/photopicker/res/values-bg/strings.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Copyright (C) 2023 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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="picker_launch_tab_option" msgid="9190702440227174597">"ЗАДАВАНЕ НА РАЗДЕЛ ЗА СТАРТИРАНЕ НА ИНСТРУМЕНТА ЗА ИЗБОР"</string>
+</resources>
diff --git a/tools/photopicker/res/values-bn/strings.xml b/tools/photopicker/res/values-bn/strings.xml
new file mode 100644
index 0000000..509985f
--- /dev/null
+++ b/tools/photopicker/res/values-bn/strings.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Copyright (C) 2023 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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="picker_launch_tab_option" msgid="9190702440227174597">"পিকার লঞ্চ ট্যাব সেট করুন"</string>
+</resources>
diff --git a/tools/photopicker/res/values-bs/strings.xml b/tools/photopicker/res/values-bs/strings.xml
new file mode 100644
index 0000000..4ebfcc7
--- /dev/null
+++ b/tools/photopicker/res/values-bs/strings.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Copyright (C) 2023 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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="picker_launch_tab_option" msgid="9190702440227174597">"POSTAVI KARTICU POKRETANJA BIRAČA"</string>
+</resources>
diff --git a/tools/photopicker/res/values-ca/strings.xml b/tools/photopicker/res/values-ca/strings.xml
new file mode 100644
index 0000000..38862b1
--- /dev/null
+++ b/tools/photopicker/res/values-ca/strings.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Copyright (C) 2023 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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="picker_launch_tab_option" msgid="9190702440227174597">"ESTABLEIX LA PESTANYA DE LLANÇAMENT DEL SELECTOR"</string>
+</resources>
diff --git a/tools/photopicker/res/values-cs/strings.xml b/tools/photopicker/res/values-cs/strings.xml
new file mode 100644
index 0000000..53a8d63
--- /dev/null
+++ b/tools/photopicker/res/values-cs/strings.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Copyright (C) 2023 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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="picker_launch_tab_option" msgid="9190702440227174597">"VÝBĚR KARTY KE SPUŠTĚNÍ VÝBĚRU"</string>
+</resources>
diff --git a/tools/photopicker/res/values-da/strings.xml b/tools/photopicker/res/values-da/strings.xml
new file mode 100644
index 0000000..97820c3
--- /dev/null
+++ b/tools/photopicker/res/values-da/strings.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Copyright (C) 2023 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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="picker_launch_tab_option" msgid="9190702440227174597">"VÆLG FANEN TIL OPSTART AF BILLEDVÆLGEREN"</string>
+</resources>
diff --git a/tools/photopicker/res/values-de/strings.xml b/tools/photopicker/res/values-de/strings.xml
new file mode 100644
index 0000000..fa3c015
--- /dev/null
+++ b/tools/photopicker/res/values-de/strings.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Copyright (C) 2023 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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="picker_launch_tab_option" msgid="9190702440227174597">"TAB FÜR STARTEN DER BILDAUSWAHL FESTLEGEN"</string>
+</resources>
diff --git a/tools/photopicker/res/values-el/strings.xml b/tools/photopicker/res/values-el/strings.xml
new file mode 100644
index 0000000..ce2d4cb
--- /dev/null
+++ b/tools/photopicker/res/values-el/strings.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Copyright (C) 2023 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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="picker_launch_tab_option" msgid="9190702440227174597">"ΟΡΙΣΜΟΣ ΚΑΡΤΕΛΑΣ ΕΚΚΙΝΗΣΗΣ ΕΡΓΑΛΕΙΟΥ ΕΠΙΛΟΓΗΣ"</string>
+</resources>
diff --git a/tools/photopicker/res/values-en-rAU/strings.xml b/tools/photopicker/res/values-en-rAU/strings.xml
new file mode 100644
index 0000000..e2f3e06
--- /dev/null
+++ b/tools/photopicker/res/values-en-rAU/strings.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Copyright (C) 2023 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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="picker_launch_tab_option" msgid="9190702440227174597">"SET PICKER LAUNCH TAB"</string>
+</resources>
diff --git a/tools/photopicker/res/values-en-rCA/strings.xml b/tools/photopicker/res/values-en-rCA/strings.xml
new file mode 100644
index 0000000..e2f3e06
--- /dev/null
+++ b/tools/photopicker/res/values-en-rCA/strings.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Copyright (C) 2023 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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="picker_launch_tab_option" msgid="9190702440227174597">"SET PICKER LAUNCH TAB"</string>
+</resources>
diff --git a/tools/photopicker/res/values-en-rGB/strings.xml b/tools/photopicker/res/values-en-rGB/strings.xml
new file mode 100644
index 0000000..e2f3e06
--- /dev/null
+++ b/tools/photopicker/res/values-en-rGB/strings.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Copyright (C) 2023 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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="picker_launch_tab_option" msgid="9190702440227174597">"SET PICKER LAUNCH TAB"</string>
+</resources>
diff --git a/tools/photopicker/res/values-en-rIN/strings.xml b/tools/photopicker/res/values-en-rIN/strings.xml
new file mode 100644
index 0000000..e2f3e06
--- /dev/null
+++ b/tools/photopicker/res/values-en-rIN/strings.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Copyright (C) 2023 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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="picker_launch_tab_option" msgid="9190702440227174597">"SET PICKER LAUNCH TAB"</string>
+</resources>
diff --git a/tools/photopicker/res/values-en-rXC/strings.xml b/tools/photopicker/res/values-en-rXC/strings.xml
new file mode 100644
index 0000000..408091c
--- /dev/null
+++ b/tools/photopicker/res/values-en-rXC/strings.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Copyright (C) 2023 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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="picker_launch_tab_option" msgid="9190702440227174597">"SET PICKER LAUNCH TAB"</string>
+</resources>
diff --git a/tools/photopicker/res/values-es-rUS/strings.xml b/tools/photopicker/res/values-es-rUS/strings.xml
new file mode 100644
index 0000000..54cbba2
--- /dev/null
+++ b/tools/photopicker/res/values-es-rUS/strings.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Copyright (C) 2023 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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="picker_launch_tab_option" msgid="9190702440227174597">"ESTABLECER LA PESTAÑA DE LANZAMIENTO DEL SELECTOR"</string>
+</resources>
diff --git a/tools/photopicker/res/values-es/strings.xml b/tools/photopicker/res/values-es/strings.xml
new file mode 100644
index 0000000..46f6e13
--- /dev/null
+++ b/tools/photopicker/res/values-es/strings.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Copyright (C) 2023 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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="picker_launch_tab_option" msgid="9190702440227174597">"PESTAÑA DE ACTIVACIÓN DEL SELECTOR"</string>
+</resources>
diff --git a/tools/photopicker/res/values-et/strings.xml b/tools/photopicker/res/values-et/strings.xml
new file mode 100644
index 0000000..8b5620c
--- /dev/null
+++ b/tools/photopicker/res/values-et/strings.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Copyright (C) 2023 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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="picker_launch_tab_option" msgid="9190702440227174597">"MÄÄRA VALIJA AVAMISE VAHEKAART"</string>
+</resources>
diff --git a/tools/photopicker/res/values-eu/strings.xml b/tools/photopicker/res/values-eu/strings.xml
new file mode 100644
index 0000000..c61d1bb
--- /dev/null
+++ b/tools/photopicker/res/values-eu/strings.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Copyright (C) 2023 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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="picker_launch_tab_option" msgid="9190702440227174597">"EZARRI HAUTATZAILEA EXEKUTATZEKO FITXA"</string>
+</resources>
diff --git a/tools/photopicker/res/values-fa/strings.xml b/tools/photopicker/res/values-fa/strings.xml
new file mode 100644
index 0000000..0f94646
--- /dev/null
+++ b/tools/photopicker/res/values-fa/strings.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Copyright (C) 2023 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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="picker_launch_tab_option" msgid="9190702440227174597">"تنظیم برگه راهاندازی انتخابگر"</string>
+</resources>
diff --git a/tools/photopicker/res/values-fi/strings.xml b/tools/photopicker/res/values-fi/strings.xml
new file mode 100644
index 0000000..6e89289
--- /dev/null
+++ b/tools/photopicker/res/values-fi/strings.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Copyright (C) 2023 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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="picker_launch_tab_option" msgid="9190702440227174597">"MÄÄRITÄ VÄLILEHTI, JOLLA VALITSIN KÄYNNISTETÄÄN"</string>
+</resources>
diff --git a/tools/photopicker/res/values-fr-rCA/strings.xml b/tools/photopicker/res/values-fr-rCA/strings.xml
new file mode 100644
index 0000000..b23e282
--- /dev/null
+++ b/tools/photopicker/res/values-fr-rCA/strings.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Copyright (C) 2023 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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="picker_launch_tab_option" msgid="9190702440227174597">"COCHER L\'ONGLET DE LANCEMENT DU SÉLECTEUR"</string>
+</resources>
diff --git a/tools/photopicker/res/values-fr/strings.xml b/tools/photopicker/res/values-fr/strings.xml
new file mode 100644
index 0000000..2623f7b
--- /dev/null
+++ b/tools/photopicker/res/values-fr/strings.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Copyright (C) 2023 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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="picker_launch_tab_option" msgid="9190702440227174597">"SÉLECTIONNER COMME ONGLET DE LANCEMENT DU SÉLECTEUR"</string>
+</resources>
diff --git a/tools/photopicker/res/values-gl/strings.xml b/tools/photopicker/res/values-gl/strings.xml
new file mode 100644
index 0000000..c158d69
--- /dev/null
+++ b/tools/photopicker/res/values-gl/strings.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Copyright (C) 2023 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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="picker_launch_tab_option" msgid="9190702440227174597">"SELECCIONAR PESTANA DE LANZAMENTO DO SELECTOR"</string>
+</resources>
diff --git a/tools/photopicker/res/values-gu/strings.xml b/tools/photopicker/res/values-gu/strings.xml
new file mode 100644
index 0000000..f2e8812
--- /dev/null
+++ b/tools/photopicker/res/values-gu/strings.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Copyright (C) 2023 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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="picker_launch_tab_option" msgid="9190702440227174597">"પિકર માટે લૉન્ચ ટૅબ સેટ કરો"</string>
+</resources>
diff --git a/tools/photopicker/res/values-hi/strings.xml b/tools/photopicker/res/values-hi/strings.xml
new file mode 100644
index 0000000..b250a29
--- /dev/null
+++ b/tools/photopicker/res/values-hi/strings.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Copyright (C) 2023 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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="picker_launch_tab_option" msgid="9190702440227174597">"पिकर के लिए लॉन्च टैब सेट करें"</string>
+</resources>
diff --git a/tools/photopicker/res/values-hr/strings.xml b/tools/photopicker/res/values-hr/strings.xml
new file mode 100644
index 0000000..2219c9a
--- /dev/null
+++ b/tools/photopicker/res/values-hr/strings.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Copyright (C) 2023 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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="picker_launch_tab_option" msgid="9190702440227174597">"POSTAVI KARTICU ZA POKRETANJE ALATA ZA ODABIR"</string>
+</resources>
diff --git a/tools/photopicker/res/values-hu/strings.xml b/tools/photopicker/res/values-hu/strings.xml
new file mode 100644
index 0000000..2cf0384
--- /dev/null
+++ b/tools/photopicker/res/values-hu/strings.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Copyright (C) 2023 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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="picker_launch_tab_option" msgid="9190702440227174597">"VÁLASZTÓINDÍTÓ LAP BEÁLLÍTÁSA"</string>
+</resources>
diff --git a/tools/photopicker/res/values-hy/strings.xml b/tools/photopicker/res/values-hy/strings.xml
new file mode 100644
index 0000000..10651f2
--- /dev/null
+++ b/tools/photopicker/res/values-hy/strings.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Copyright (C) 2023 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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="picker_launch_tab_option" msgid="9190702440227174597">"ՆՇԵԼ ԸՆՏՐԻՉԻ ԳՈՐԾԱՐԿՄԱՆ ՆԵՐԴԻՐԸ"</string>
+</resources>
diff --git a/tools/photopicker/res/values-in/strings.xml b/tools/photopicker/res/values-in/strings.xml
new file mode 100644
index 0000000..b0d6cb0
--- /dev/null
+++ b/tools/photopicker/res/values-in/strings.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Copyright (C) 2023 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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="picker_launch_tab_option" msgid="9190702440227174597">"SETEL TAB PELUNCURAN PEMILIH"</string>
+</resources>
diff --git a/tools/photopicker/res/values-is/strings.xml b/tools/photopicker/res/values-is/strings.xml
new file mode 100644
index 0000000..b80b827
--- /dev/null
+++ b/tools/photopicker/res/values-is/strings.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Copyright (C) 2023 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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="picker_launch_tab_option" msgid="9190702440227174597">"VELDU FLIPA TIL AÐ RÆSA VAL"</string>
+</resources>
diff --git a/tools/photopicker/res/values-it/strings.xml b/tools/photopicker/res/values-it/strings.xml
new file mode 100644
index 0000000..f97ca63
--- /dev/null
+++ b/tools/photopicker/res/values-it/strings.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Copyright (C) 2023 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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="picker_launch_tab_option" msgid="9190702440227174597">"SELEZIONA LA SCHEDA DI LANCIO DEL SELETTORE"</string>
+</resources>
diff --git a/tools/photopicker/res/values-iw/strings.xml b/tools/photopicker/res/values-iw/strings.xml
new file mode 100644
index 0000000..dd94d12
--- /dev/null
+++ b/tools/photopicker/res/values-iw/strings.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Copyright (C) 2023 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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="picker_launch_tab_option" msgid="9190702440227174597">"הגדרה של כרטיסיית ההפעלה של הבורר"</string>
+</resources>
diff --git a/tools/photopicker/res/values-ja/strings.xml b/tools/photopicker/res/values-ja/strings.xml
new file mode 100644
index 0000000..d8bc6a1
--- /dev/null
+++ b/tools/photopicker/res/values-ja/strings.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Copyright (C) 2023 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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="picker_launch_tab_option" msgid="9190702440227174597">"選択ツール起動タブを選択する"</string>
+</resources>
diff --git a/tools/photopicker/res/values-ka/strings.xml b/tools/photopicker/res/values-ka/strings.xml
new file mode 100644
index 0000000..5a8291d
--- /dev/null
+++ b/tools/photopicker/res/values-ka/strings.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Copyright (C) 2023 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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="picker_launch_tab_option" msgid="9190702440227174597">"ამომრჩევის გაშვების ჩანართის დაყენება"</string>
+</resources>
diff --git a/tools/photopicker/res/values-kk/strings.xml b/tools/photopicker/res/values-kk/strings.xml
new file mode 100644
index 0000000..45dd18b
--- /dev/null
+++ b/tools/photopicker/res/values-kk/strings.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Copyright (C) 2023 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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="picker_launch_tab_option" msgid="9190702440227174597">"ТАҢДАУ ҚҰРАЛЫН ІСКЕ ҚОСУ ҚОЙЫНДЫСЫН ОРНАТУ"</string>
+</resources>
diff --git a/tools/photopicker/res/values-km/strings.xml b/tools/photopicker/res/values-km/strings.xml
new file mode 100644
index 0000000..2393624
--- /dev/null
+++ b/tools/photopicker/res/values-km/strings.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Copyright (C) 2023 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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="picker_launch_tab_option" msgid="9190702440227174597">"កំណត់ផ្ទាំងបើកដំណើរការឧបករណ៍ជ្រើសរើស"</string>
+</resources>
diff --git a/tools/photopicker/res/values-kn/strings.xml b/tools/photopicker/res/values-kn/strings.xml
new file mode 100644
index 0000000..9631710
--- /dev/null
+++ b/tools/photopicker/res/values-kn/strings.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Copyright (C) 2023 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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="picker_launch_tab_option" msgid="9190702440227174597">"ಪಿಕರ್ ಲಾಂಚ್ ಟ್ಯಾಬ್ ಅನ್ನು ಸೆಟ್ ಮಾಡಿ"</string>
+</resources>
diff --git a/tools/photopicker/res/values-ko/strings.xml b/tools/photopicker/res/values-ko/strings.xml
new file mode 100644
index 0000000..708df17
--- /dev/null
+++ b/tools/photopicker/res/values-ko/strings.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Copyright (C) 2023 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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="picker_launch_tab_option" msgid="9190702440227174597">"선택 도구 실행 탭 설정"</string>
+</resources>
diff --git a/tools/photopicker/res/values-ky/strings.xml b/tools/photopicker/res/values-ky/strings.xml
new file mode 100644
index 0000000..0065249
--- /dev/null
+++ b/tools/photopicker/res/values-ky/strings.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Copyright (C) 2023 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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="picker_launch_tab_option" msgid="9190702440227174597">"ТАНДАГЫЧТЫ ИШТЕТҮҮ ӨТМӨГҮН ТУУРАЛОО"</string>
+</resources>
diff --git a/tools/photopicker/res/values-lo/strings.xml b/tools/photopicker/res/values-lo/strings.xml
new file mode 100644
index 0000000..7745709
--- /dev/null
+++ b/tools/photopicker/res/values-lo/strings.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Copyright (C) 2023 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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="picker_launch_tab_option" msgid="9190702440227174597">"ຕັ້ງແຖບເປີດໃຊ້ຕົວເລືອກ"</string>
+</resources>
diff --git a/tools/photopicker/res/values-lt/strings.xml b/tools/photopicker/res/values-lt/strings.xml
new file mode 100644
index 0000000..44ab86d
--- /dev/null
+++ b/tools/photopicker/res/values-lt/strings.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Copyright (C) 2023 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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="picker_launch_tab_option" msgid="9190702440227174597">"NUSTATYTI RINKIKLIO PALEIDIMO SKIRTUKĄ"</string>
+</resources>
diff --git a/tools/photopicker/res/values-lv/strings.xml b/tools/photopicker/res/values-lv/strings.xml
new file mode 100644
index 0000000..7835b65
--- /dev/null
+++ b/tools/photopicker/res/values-lv/strings.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Copyright (C) 2023 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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="picker_launch_tab_option" msgid="9190702440227174597">"IESTATĪT KĀ CILNI, KURĀ PALAIST ATLASĪTĀJU"</string>
+</resources>
diff --git a/tools/photopicker/res/values-mk/strings.xml b/tools/photopicker/res/values-mk/strings.xml
new file mode 100644
index 0000000..3abc5a0
--- /dev/null
+++ b/tools/photopicker/res/values-mk/strings.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Copyright (C) 2023 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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="picker_launch_tab_option" msgid="9190702440227174597">"ИЗБЕРИ КАРТИЧКА ЗА СТАРТУВАЊЕ ИЗБИРАЧ"</string>
+</resources>
diff --git a/tools/photopicker/res/values-ml/strings.xml b/tools/photopicker/res/values-ml/strings.xml
new file mode 100644
index 0000000..33c2c0e
--- /dev/null
+++ b/tools/photopicker/res/values-ml/strings.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Copyright (C) 2023 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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="picker_launch_tab_option" msgid="9190702440227174597">"പിക്കർ ലോഞ്ച് ടാബ് സജ്ജീകരിക്കുക"</string>
+</resources>
diff --git a/tools/photopicker/res/values-mn/strings.xml b/tools/photopicker/res/values-mn/strings.xml
new file mode 100644
index 0000000..bfdc858
--- /dev/null
+++ b/tools/photopicker/res/values-mn/strings.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Copyright (C) 2023 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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="picker_launch_tab_option" msgid="9190702440227174597">"СОНГОГЧИЙГ ЭХЛҮҮЛЭХ ТАБ ТОХИРУУЛАХ"</string>
+</resources>
diff --git a/tools/photopicker/res/values-mr/strings.xml b/tools/photopicker/res/values-mr/strings.xml
new file mode 100644
index 0000000..39facc6
--- /dev/null
+++ b/tools/photopicker/res/values-mr/strings.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Copyright (C) 2023 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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="picker_launch_tab_option" msgid="9190702440227174597">"पिकर लाँच टॅब सेट करा"</string>
+</resources>
diff --git a/tools/photopicker/res/values-ms/strings.xml b/tools/photopicker/res/values-ms/strings.xml
new file mode 100644
index 0000000..d94e78a
--- /dev/null
+++ b/tools/photopicker/res/values-ms/strings.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Copyright (C) 2023 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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="picker_launch_tab_option" msgid="9190702440227174597">"TETAPKAN TAB PELANCARAN PEMILIH"</string>
+</resources>
diff --git a/tools/photopicker/res/values-my/strings.xml b/tools/photopicker/res/values-my/strings.xml
new file mode 100644
index 0000000..20fdedf
--- /dev/null
+++ b/tools/photopicker/res/values-my/strings.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Copyright (C) 2023 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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="picker_launch_tab_option" msgid="9190702440227174597">"ရွေးချယ်ရေးစနစ် စတင်ခြင်းတဘ် သတ်မှတ်ရန်"</string>
+</resources>
diff --git a/tools/photopicker/res/values-nb/strings.xml b/tools/photopicker/res/values-nb/strings.xml
new file mode 100644
index 0000000..2cdccb6
--- /dev/null
+++ b/tools/photopicker/res/values-nb/strings.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Copyright (C) 2023 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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="picker_launch_tab_option" msgid="9190702440227174597">"ANGI STARTFANE FOR VELGEREN"</string>
+</resources>
diff --git a/tools/photopicker/res/values-ne/strings.xml b/tools/photopicker/res/values-ne/strings.xml
new file mode 100644
index 0000000..ce5b128
--- /dev/null
+++ b/tools/photopicker/res/values-ne/strings.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Copyright (C) 2023 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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="picker_launch_tab_option" msgid="9190702440227174597">"यो ट्याबलाई पिकर खोल्दा सुरुमा खुल्ने ट्याब बनाउनुहोस्"</string>
+</resources>
diff --git a/tools/photopicker/res/values-nl/strings.xml b/tools/photopicker/res/values-nl/strings.xml
new file mode 100644
index 0000000..891f927
--- /dev/null
+++ b/tools/photopicker/res/values-nl/strings.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Copyright (C) 2023 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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="picker_launch_tab_option" msgid="9190702440227174597">"TABBLAD VOOR KIEZER STARTEN INSTELLEN"</string>
+</resources>
diff --git a/tools/photopicker/res/values-or/strings.xml b/tools/photopicker/res/values-or/strings.xml
new file mode 100644
index 0000000..f9e6e14
--- /dev/null
+++ b/tools/photopicker/res/values-or/strings.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Copyright (C) 2023 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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="picker_launch_tab_option" msgid="9190702440227174597">"ପିକର ଲଞ୍ଚ ଟାବ ସେଟ କରନ୍ତୁ"</string>
+</resources>
diff --git a/tools/photopicker/res/values-pa/strings.xml b/tools/photopicker/res/values-pa/strings.xml
new file mode 100644
index 0000000..bc147dd
--- /dev/null
+++ b/tools/photopicker/res/values-pa/strings.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Copyright (C) 2023 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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="picker_launch_tab_option" msgid="9190702440227174597">"ਚੋਣਕਾਰ ਲਾਂਚ ਟੈਬ ਨੂੰ ਸੈੱਟ ਕਰੋ"</string>
+</resources>
diff --git a/tools/photopicker/res/values-pl/strings.xml b/tools/photopicker/res/values-pl/strings.xml
new file mode 100644
index 0000000..e263848
--- /dev/null
+++ b/tools/photopicker/res/values-pl/strings.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Copyright (C) 2023 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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="picker_launch_tab_option" msgid="9190702440227174597">"USTAW KARTĘ URUCHAMIANIA SELEKTORA"</string>
+</resources>
diff --git a/tools/photopicker/res/values-pt-rBR/strings.xml b/tools/photopicker/res/values-pt-rBR/strings.xml
new file mode 100644
index 0000000..038edcd
--- /dev/null
+++ b/tools/photopicker/res/values-pt-rBR/strings.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Copyright (C) 2023 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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="picker_launch_tab_option" msgid="9190702440227174597">"CONFIGURAR GUIA INICIAL DO SELETOR"</string>
+</resources>
diff --git a/tools/photopicker/res/values-pt-rPT/strings.xml b/tools/photopicker/res/values-pt-rPT/strings.xml
new file mode 100644
index 0000000..1c5cf44
--- /dev/null
+++ b/tools/photopicker/res/values-pt-rPT/strings.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Copyright (C) 2023 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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="picker_launch_tab_option" msgid="9190702440227174597">"SEPARADOR DEFINIR LANÇAMENTO DO SELECIONADOR"</string>
+</resources>
diff --git a/tools/photopicker/res/values-pt/strings.xml b/tools/photopicker/res/values-pt/strings.xml
new file mode 100644
index 0000000..038edcd
--- /dev/null
+++ b/tools/photopicker/res/values-pt/strings.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Copyright (C) 2023 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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="picker_launch_tab_option" msgid="9190702440227174597">"CONFIGURAR GUIA INICIAL DO SELETOR"</string>
+</resources>
diff --git a/tools/photopicker/res/values-ro/strings.xml b/tools/photopicker/res/values-ro/strings.xml
new file mode 100644
index 0000000..6e63303
--- /dev/null
+++ b/tools/photopicker/res/values-ro/strings.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Copyright (C) 2023 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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="picker_launch_tab_option" msgid="9190702440227174597">"SETEAZĂ FILA DE LANSARE A SELECTORULUI"</string>
+</resources>
diff --git a/tools/photopicker/res/values-ru/strings.xml b/tools/photopicker/res/values-ru/strings.xml
new file mode 100644
index 0000000..c74790f
--- /dev/null
+++ b/tools/photopicker/res/values-ru/strings.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Copyright (C) 2023 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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="picker_launch_tab_option" msgid="9190702440227174597">"ИСПОЛЬЗОВАТЬ ЭТУ ВКЛАДКУ ДЛЯ ЗАПУСКА"</string>
+</resources>
diff --git a/tools/photopicker/res/values-si/strings.xml b/tools/photopicker/res/values-si/strings.xml
new file mode 100644
index 0000000..c2638d0
--- /dev/null
+++ b/tools/photopicker/res/values-si/strings.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Copyright (C) 2023 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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="picker_launch_tab_option" msgid="9190702440227174597">"තෝරක දියත් කිරීමේ පටිත්ත සකසන්න"</string>
+</resources>
diff --git a/tools/photopicker/res/values-sk/strings.xml b/tools/photopicker/res/values-sk/strings.xml
new file mode 100644
index 0000000..75e0535
--- /dev/null
+++ b/tools/photopicker/res/values-sk/strings.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Copyright (C) 2023 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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="picker_launch_tab_option" msgid="9190702440227174597">"NASTAVIŤ KARTU SPUSTENIA VÝBERU"</string>
+</resources>
diff --git a/tools/photopicker/res/values-sl/strings.xml b/tools/photopicker/res/values-sl/strings.xml
new file mode 100644
index 0000000..0f022cd
--- /dev/null
+++ b/tools/photopicker/res/values-sl/strings.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Copyright (C) 2023 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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="picker_launch_tab_option" msgid="9190702440227174597">"NASTAVI ZAVIHEK ZA ZAGON IZBIRNIKA"</string>
+</resources>
diff --git a/tools/photopicker/res/values-sq/strings.xml b/tools/photopicker/res/values-sq/strings.xml
new file mode 100644
index 0000000..dc3723a
--- /dev/null
+++ b/tools/photopicker/res/values-sq/strings.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Copyright (C) 2023 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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="picker_launch_tab_option" msgid="9190702440227174597">"CAKTO SKEDËN PËR HAPJEN E ZGJEDHËSIT"</string>
+</resources>
diff --git a/tools/photopicker/res/values-sr/strings.xml b/tools/photopicker/res/values-sr/strings.xml
new file mode 100644
index 0000000..3b9a510
--- /dev/null
+++ b/tools/photopicker/res/values-sr/strings.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Copyright (C) 2023 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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="picker_launch_tab_option" msgid="9190702440227174597">"ПОДЕШАВАЊЕ КАРТИЦЕ ЗА ПОКРЕТАЊЕ БИРАЧА"</string>
+</resources>
diff --git a/tools/photopicker/res/values-sv/strings.xml b/tools/photopicker/res/values-sv/strings.xml
new file mode 100644
index 0000000..973801e
--- /dev/null
+++ b/tools/photopicker/res/values-sv/strings.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Copyright (C) 2023 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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="picker_launch_tab_option" msgid="9190702440227174597">"STÄLL IN STARTFLIK FÖR VÄLJAREN"</string>
+</resources>
diff --git a/tools/photopicker/res/values-sw/strings.xml b/tools/photopicker/res/values-sw/strings.xml
new file mode 100644
index 0000000..7f598ca
--- /dev/null
+++ b/tools/photopicker/res/values-sw/strings.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Copyright (C) 2023 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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="picker_launch_tab_option" msgid="9190702440227174597">"WEKA KICHUPO CHA KUWASHA PICKER"</string>
+</resources>
diff --git a/tools/photopicker/res/values-ta/strings.xml b/tools/photopicker/res/values-ta/strings.xml
new file mode 100644
index 0000000..bf500c7
--- /dev/null
+++ b/tools/photopicker/res/values-ta/strings.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Copyright (C) 2023 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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="picker_launch_tab_option" msgid="9190702440227174597">"தேர்வுக் கருவியைத் தொடங்குவதற்கான பகுதியை அமைத்தல்"</string>
+</resources>
diff --git a/tools/photopicker/res/values-te/strings.xml b/tools/photopicker/res/values-te/strings.xml
new file mode 100644
index 0000000..d6ccb75
--- /dev/null
+++ b/tools/photopicker/res/values-te/strings.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Copyright (C) 2023 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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="picker_launch_tab_option" msgid="9190702440227174597">"పికర్ లాంచ్ ట్యాబ్ను సెట్ చేయండి"</string>
+</resources>
diff --git a/tools/photopicker/res/values-th/strings.xml b/tools/photopicker/res/values-th/strings.xml
new file mode 100644
index 0000000..ab7c02c
--- /dev/null
+++ b/tools/photopicker/res/values-th/strings.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Copyright (C) 2023 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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="picker_launch_tab_option" msgid="9190702440227174597">"ตั้งค่าแท็บเปิดใช้งานเครื่องมือเลือก"</string>
+</resources>
diff --git a/tools/photopicker/res/values-tl/strings.xml b/tools/photopicker/res/values-tl/strings.xml
new file mode 100644
index 0000000..4175d86
--- /dev/null
+++ b/tools/photopicker/res/values-tl/strings.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Copyright (C) 2023 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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="picker_launch_tab_option" msgid="9190702440227174597">"ITAKDA ANG TAB NG PAGLULUNSAD NG PICKER"</string>
+</resources>
diff --git a/tools/photopicker/res/values-tr/strings.xml b/tools/photopicker/res/values-tr/strings.xml
new file mode 100644
index 0000000..62a203a
--- /dev/null
+++ b/tools/photopicker/res/values-tr/strings.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Copyright (C) 2023 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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="picker_launch_tab_option" msgid="9190702440227174597">"SEÇİCİ BAŞLATMA SEKMESİNİ AYARLA"</string>
+</resources>
diff --git a/tools/photopicker/res/values-uk/strings.xml b/tools/photopicker/res/values-uk/strings.xml
new file mode 100644
index 0000000..b1a6c28
--- /dev/null
+++ b/tools/photopicker/res/values-uk/strings.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Copyright (C) 2023 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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="picker_launch_tab_option" msgid="9190702440227174597">"НАЛАШТУВАТИ ВКЛАДКУ ДЛЯ ЗАПУСКУ ЗАСОБУ ВИБОРУ"</string>
+</resources>
diff --git a/tools/photopicker/res/values-ur/strings.xml b/tools/photopicker/res/values-ur/strings.xml
new file mode 100644
index 0000000..f5a0771
--- /dev/null
+++ b/tools/photopicker/res/values-ur/strings.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Copyright (C) 2023 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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="picker_launch_tab_option" msgid="9190702440227174597">"منتخب کنندہ کا لانچ ٹیب سیٹ کریں"</string>
+</resources>
diff --git a/tools/photopicker/res/values-uz/strings.xml b/tools/photopicker/res/values-uz/strings.xml
new file mode 100644
index 0000000..d8a0293
--- /dev/null
+++ b/tools/photopicker/res/values-uz/strings.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Copyright (C) 2023 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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="picker_launch_tab_option" msgid="9190702440227174597">"TANLAGICH OCHILADIGAN VARAQ"</string>
+</resources>
diff --git a/tools/photopicker/res/values-vi/strings.xml b/tools/photopicker/res/values-vi/strings.xml
new file mode 100644
index 0000000..f5e7153
--- /dev/null
+++ b/tools/photopicker/res/values-vi/strings.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Copyright (C) 2023 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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="picker_launch_tab_option" msgid="9190702440227174597">"THIẾT LẬP THẺ KHỞI CHẠY BỘ CHỌN"</string>
+</resources>
diff --git a/tools/photopicker/res/values-zh-rCN/strings.xml b/tools/photopicker/res/values-zh-rCN/strings.xml
new file mode 100644
index 0000000..1390509
--- /dev/null
+++ b/tools/photopicker/res/values-zh-rCN/strings.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Copyright (C) 2023 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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="picker_launch_tab_option" msgid="9190702440227174597">"设定选择器启动标签页"</string>
+</resources>
diff --git a/tools/photopicker/res/values-zh-rHK/strings.xml b/tools/photopicker/res/values-zh-rHK/strings.xml
new file mode 100644
index 0000000..51a1b80
--- /dev/null
+++ b/tools/photopicker/res/values-zh-rHK/strings.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Copyright (C) 2023 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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="picker_launch_tab_option" msgid="9190702440227174597">"設定挑選器啟動分頁"</string>
+</resources>
diff --git a/tools/photopicker/res/values-zh-rTW/strings.xml b/tools/photopicker/res/values-zh-rTW/strings.xml
new file mode 100644
index 0000000..51a1b80
--- /dev/null
+++ b/tools/photopicker/res/values-zh-rTW/strings.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Copyright (C) 2023 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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="picker_launch_tab_option" msgid="9190702440227174597">"設定挑選器啟動分頁"</string>
+</resources>
diff --git a/tools/photopicker/res/values-zu/strings.xml b/tools/photopicker/res/values-zu/strings.xml
new file mode 100644
index 0000000..933a978
--- /dev/null
+++ b/tools/photopicker/res/values-zu/strings.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Copyright (C) 2023 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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="picker_launch_tab_option" msgid="9190702440227174597">"SETHA ITHEBHU YOKUQALISA ISIKHETHI"</string>
+</resources>