1 /* 2 * Copyright 2022 Google LLC 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 package com.google.android.libraries.mobiledatadownload.file.backends; 17 18 import android.accounts.Account; 19 import com.google.android.libraries.mobiledatadownload.file.common.internal.Preconditions; 20 21 /** Helper for Uri classes to serialize Android accounts. */ 22 public final class AccountSerialization { 23 24 private static final String SHARED_ACCOUNT_STR = "shared"; 25 26 /** A common {@link Account} with no associated user; it appears as "shared" on the filesystem. */ 27 public static final Account SHARED_ACCOUNT = new Account(SHARED_ACCOUNT_STR, "mobstore"); 28 29 /** 30 * Validates and serializes an {@link Account} into a string representation "<type>:<name>", with 31 * the exception of {@link #SHARED_ACCOUNT} which is serialized as {@link #SHARED_ACCOUNT_STR}. 32 * 33 * <p>The account will be URL encoded in its URI representation (so, eg, "<internal>@gmail.com" 34 * will appear as "you%40gmail.com"), but not in the file path representation used to access disk. 35 * {@link #SHARED_ACCOUNT} shows up as "shared" on the filesystem. 36 * 37 * <p>This method performs some account validation. Android Account itself requires that both the 38 * type and name fields be present. In addition to this requirement, this method requires that the 39 * type contain no colons (as these are the delimiter used internally for the account 40 * serialization), and that neither the type nor the name include any slashes (as these are file 41 * separators). 42 * 43 * <p>Note the Linux filesystem accepts filenames composed of any bytes except "/" and NULL. 44 * 45 * @throws IllegalArgumentException if the account does not conform to the specifications above 46 */ serialize(Account account)47 public static String serialize(Account account) { 48 validate(account); 49 if (isSharedAccount(account)) { 50 return SHARED_ACCOUNT_STR; 51 } 52 return account.type + ":" + account.name; 53 } 54 55 /** 56 * Parses an account string generated by {@link #serialize} back into an {@link Account}, or 57 * throws {@link IllegalArgumentException} if {@code accountStr} has an unrecognized format. 58 */ deserialize(String accountStr)59 public static Account deserialize(String accountStr) { 60 if (isSharedAccount(accountStr)) { 61 return SHARED_ACCOUNT; 62 } 63 int colonIdx = accountStr.indexOf(':'); 64 Preconditions.checkArgument(colonIdx > -1, "Malformed account"); 65 String type = accountStr.substring(0, colonIdx); 66 String name = accountStr.substring(colonIdx + 1); 67 return new Account(name, type); 68 } 69 isSharedAccount(String accountStr)70 static boolean isSharedAccount(String accountStr) { 71 return SHARED_ACCOUNT_STR.equals(accountStr); 72 } 73 isSharedAccount(Account account)74 static boolean isSharedAccount(Account account) { 75 return SHARED_ACCOUNT.equals(account); 76 } 77 validate(Account account)78 private static void validate(Account account) { 79 // Android Account already validates that name and type are not empty. 80 Preconditions.checkArgument(account.type.indexOf(':') == -1, "Account type contains ':'."); 81 Preconditions.checkArgument(account.type.indexOf('/') == -1, "Account type contains '/'."); 82 Preconditions.checkArgument(account.name.indexOf('/') == -1, "Account name contains '/'."); 83 } 84 AccountSerialization()85 private AccountSerialization() {} 86 } 87