1/*
2 * Copyright 2016, The Android Open Source Project
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
17package com.android.managedprovisioning.common;
18
19import android.accounts.Account;
20import android.content.ComponentName;
21import android.content.ContentResolver;
22import android.content.Context;
23import android.net.Uri;
24import android.os.PersistableBundle;
25import android.util.Base64;
26import java.io.ByteArrayOutputStream;
27import java.io.File;
28import java.io.FileInputStream;
29import java.io.FileOutputStream;
30import java.io.IOException;
31import java.io.InputStream;
32import java.io.OutputStream;
33import java.util.IllformedLocaleException;
34import java.util.Locale;
35import java.util.function.Function;
36
37/**
38 * Class with Utils methods to store values in xml files, and to convert various
39 * types to and from string.
40 */
41public class StoreUtils {
42    public static final String ATTR_VALUE = "value";
43
44    /**
45     * Directory name under parent directory {@link Context#getFilesDir()}
46     * It's directory to cache all files / uri from external provisioning intent.
47     * Files must be prefixed by their own prefixes to avoid collisions.
48     */
49    public static final String DIR_PROVISIONING_PARAMS_FILE_CACHE =
50            "provisioning_params_file_cache";
51
52    private static final String ATTR_ACCOUNT_NAME = "account-name";
53    private static final String ATTR_ACCOUNT_TYPE = "account-type";
54
55    /**
56     * Reads an account from a {@link PersistableBundle}.
57     */
58    public static Account persistableBundleToAccount(PersistableBundle bundle) {
59        return new Account(
60                bundle.getString(ATTR_ACCOUNT_NAME),
61                bundle.getString(ATTR_ACCOUNT_TYPE));
62    }
63
64    /**
65     * Writes an account to a {@link PersistableBundle}.
66     */
67    public static PersistableBundle accountToPersistableBundle(Account account) {
68        final PersistableBundle bundle = new PersistableBundle();
69        bundle.putString(ATTR_ACCOUNT_NAME, account.name);
70        bundle.putString(ATTR_ACCOUNT_TYPE, account.type);
71        return bundle;
72    }
73
74    /**
75     * Serialize ComponentName.
76     */
77    public static String componentNameToString(ComponentName componentName) {
78        return componentName == null ? null
79                : componentName.getPackageName() + "/" + componentName.getClassName();
80    }
81
82    /**
83     * Deserialize ComponentName.
84     * Don't use {@link ComponentName#unflattenFromString(String)}, because it doesn't keep
85     * original class name
86     */
87    public static ComponentName stringToComponentName(String str) {
88        int sep = str.indexOf('/');
89        if (sep < 0 || (sep+1) >= str.length()) {
90            return null;
91        }
92        String pkg = str.substring(0, sep);
93        String cls = str.substring(sep+1);
94        return new ComponentName(pkg, cls);
95    }
96
97    /**
98     * Converts a String to a Locale.
99     */
100    public static Locale stringToLocale(String string) throws IllformedLocaleException {
101        if (string != null) {
102            return new Locale.Builder().setLanguageTag(string.replace("_", "-")).build();
103        } else {
104            return null;
105        }
106    }
107
108    /**
109     * Converts a Locale to a String.
110     */
111    public static String localeToString(Locale locale) {
112        if (locale != null) {
113            return locale.toLanguageTag();
114        } else {
115            return null;
116        }
117    }
118
119    /**
120     * Transforms a string into a byte array.
121     *
122     * @param s the string to be transformed
123     */
124    public static byte[] stringToByteArray(String s)
125        throws NumberFormatException {
126        try {
127            return Base64.decode(s, Base64.URL_SAFE);
128        } catch (IllegalArgumentException e) {
129            throw new NumberFormatException("Incorrect format. Should be Url-safe Base64 encoded.");
130        }
131    }
132
133    /**
134     * Transforms a byte array into a string.
135     *
136     * @param bytes the byte array to be transformed
137     */
138    public static String byteArrayToString(byte[] bytes) {
139        return Base64.encodeToString(bytes, Base64.URL_SAFE | Base64.NO_PADDING | Base64.NO_WRAP);
140    }
141
142    public static void putIntegerIfNotNull(PersistableBundle bundle, String attrName,
143            Integer integer) {
144        if (integer != null) {
145            bundle.putInt(attrName, integer);
146        }
147    }
148
149    public static void putPersistableBundlableIfNotNull(PersistableBundle bundle, String attrName,
150            PersistableBundlable bundlable) {
151        if (bundlable != null) {
152            bundle.putPersistableBundle(attrName, bundlable.toPersistableBundle());
153        }
154    }
155
156    public static <E> E getObjectAttrFromPersistableBundle(PersistableBundle bundle,
157            String attrName, Function<PersistableBundle, E> converter) {
158        final PersistableBundle attrBundle = bundle.getPersistableBundle(attrName);
159        return attrBundle == null ? null : converter.apply(attrBundle);
160    }
161
162    public static <E> E getStringAttrFromPersistableBundle(PersistableBundle bundle,
163            String attrName, Function<String, E> converter) {
164        final String str = bundle.getString(attrName);
165        return str == null ? null : converter.apply(str);
166    }
167
168    public static Integer getIntegerAttrFromPersistableBundle(PersistableBundle bundle,
169            String attrName) {
170        return bundle.containsKey(attrName) ? bundle.getInt(attrName) : null;
171    }
172
173    /**
174     * @return true if successfully copy the uri into the file. Otherwise, the outputFile will not
175     * be created.
176     */
177    public static boolean copyUriIntoFile(ContentResolver cr, Uri uri, File outputFile) {
178        try (final InputStream in = cr.openInputStream(uri)) { // Throws SecurityException
179            try (final FileOutputStream out = new FileOutputStream(outputFile)) {
180                copyStream(in, out);
181            }
182            ProvisionLogger.logi("Successfully copy from uri " + uri + " to " + outputFile);
183            return true;
184        } catch (IOException | SecurityException e) {
185            ProvisionLogger.logi("Could not write file from " + uri + " to "
186                    + outputFile, e);
187            // If the file was only partly written, delete it.
188            outputFile.delete();
189            return false;
190        }
191    }
192
193    public static String readString(File file) throws IOException {
194        try (final InputStream in = new FileInputStream(file)) {
195            try (final ByteArrayOutputStream out = new ByteArrayOutputStream()) {
196                copyStream(in, out);
197                return out.toString();
198            }
199        }
200    }
201
202    public static void copyStream(final InputStream in,
203            final OutputStream out) throws IOException {
204        final byte buffer[] = new byte[1024];
205        int bytesReadCount;
206        while ((bytesReadCount = in.read(buffer)) != -1) {
207            out.write(buffer, 0, bytesReadCount);
208        }
209    }
210
211    public interface TextFileReader {
212        String read(File file) throws IOException;
213    }
214}