1 /*
2  * Copyright (C) 2023 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 
17 package com.android.managedprovisioning.util
18 
19 import android.content.Context
20 import android.util.Log
21 import android.os.Bundle
22 import android.os.Parcel
23 import android.os.Parcelable
24 import androidx.annotation.StringRes
25 
26 
27 /** A container for a [StringRes] with format arguments that can be resolved lazily */
28 sealed class LazyStringResource : Parcelable {
29     /** Android string resource ID */
30     @get:StringRes
31     abstract val resId: Int
32 
33     /** String resource format arguments to be used when resolving */
34     abstract val formatArgs: Array<out CharSequence>
35 
36     /** Resolve the lazy value of this resource within a given [context] */
valuenull37     open fun value(context: Context): String =
38         runCatching { context.getString(resId, *formatArgs) }
<lambda>null39             .onFailure {
40                 val args = formatArgs.joinToString(",")
41                 Log.e(
42                     TAG,
43                     "Unable to resolve LazyStringResource: resId=$resId formatArgs=[$args]",
44                     it
45                 )
46             }
47             .getOrThrow()
48 
49 
50     /**
51      * Resolve the lazy value of this resource within a given [context] or return null if it cannot be
52      * resolved
53      */
<lambda>null54     fun valueOrNull(context: Context): String? = runCatching { value(context) }.getOrNull()
55 
56     /** Check if the string resource is null or blank within a given [context] */
isBlanknull57     fun isBlank(context: Context): Boolean = valueOrNull(context).isNullOrBlank()
58 
59     override fun equals(other: Any?): Boolean =
60         when {
61             this === other -> true
62             other !is LazyStringResource -> false
63             resId != other.resId -> false
64             !formatArgs.contentEquals(other.formatArgs) -> false
65             else -> true
66         }
67 
hashCodenull68     override fun hashCode(): Int {
69         var result = resId
70         result = 31 * result + formatArgs.contentHashCode()
71         return result
72     }
73 
describeContentsnull74     override fun describeContents(): Int = 0
75 
76     override fun writeToParcel(dest: Parcel, flags: Int) {
77         dest.writeInt(resId)
78         dest.writeInt(formatArgs.size)
79         dest.writeStringArray(formatArgs.map(CharSequence::toString).toTypedArray())
80     }
81 
82     /**
83      * Converts this resource to a [Bundle]
84      *
85      * @see LazyStringResource.invoke
86      */
toBundlenull87     fun toBundle(): Bundle =
88         Bundle().apply {
89             putInt(BUNDLE_RES_ID, resId)
90             putCharSequenceArray(BUNDLE_ARGS, formatArgs)
91         }
92 
toStringnull93     override fun toString(): String =
94         "${this::class.simpleName}(resId=$resId, formatArgs=${formatArgs.contentToString()})"
95 
96     companion object {
97         private const val TAG = "LazyStringResource"
98         const val BUNDLE_RES_ID = "resId"
99         const val BUNDLE_ARGS = "args"
100 
101         /** Construct a [LazyStringResource]. Returns [Raw] wrapper or [Empty] if the [resId] is `0` */
102         @JvmStatic
103         @JvmName("of")
104         operator fun invoke(@StringRes resId: Int, vararg formatArgs: CharSequence) =
105             if (resId == 0) Empty else Raw(resId, formatArgs = formatArgs)
106 
107         /**
108          * Construct a [LazyStringResource] from a given [Bundle] produced by
109          * [LazyStringResource.toBundle]. Returns [Raw] wrapper or [Empty] if the [resId] is `0`
110          */
111         @JvmStatic
112         @JvmName("of")
113         operator fun invoke(bundle: Bundle): LazyStringResource =
114             invoke(
115                 bundle.getInt(BUNDLE_RES_ID),
116                 formatArgs = bundle.getCharSequenceArray(BUNDLE_ARGS)
117                     ?: error("Unable to read formatArgs from a bundle")
118             )
119 
120         /** Construct a [LazyStringResource]. Returns [Static] wrapper */
121         @JvmStatic
122         @JvmName("of")
123         operator fun invoke(value: String) = Static(value)
124 
125         @JvmField
126         val CREATOR: Parcelable.Creator<LazyStringResource> =
127             object : Parcelable.Creator<LazyStringResource> {
128                 override fun createFromParcel(parcel: Parcel): LazyStringResource {
129                     return LazyStringResource(
130                         resId = parcel.readInt(),
131                         formatArgs =
132                         arrayOfNulls<String>(parcel.readInt())
133                             .apply(parcel::readStringArray)
134                             .filterNotNull()
135                             .toTypedArray()
136                     )
137                 }
138 
139                 override fun newArray(size: Int): Array<LazyStringResource?> = arrayOfNulls(size)
140             }
141     }
142 
143     /** A [LazyStringResource] that can be resolved within a given [Context] */
144     class Raw
145     internal constructor(
146         @StringRes override val resId: Int,
147         override vararg val formatArgs: CharSequence,
148     ) : LazyStringResource()
149 
150     /** A [LazyStringResource] that has a static string value */
151     class Static internal constructor(val value: String) : LazyStringResource() {
152         override val resId: Int = 0
153         override val formatArgs: Array<out CharSequence> = emptyArray()
valuenull154         override fun value(context: Context): String = value
155     }
156 
157     /** A [LazyStringResource] that has an empty hard-coded value */
158     object Empty : LazyStringResource() {
159         override val resId: Int = 0
160         override val formatArgs: Array<out CharSequence> = emptyArray()
161         override fun value(context: Context): String = ""
162     }
163 }
164 
165 /** Resolves [LazyStringResource] within a current context. */
Contextnull166 fun Context.getString(resource: LazyStringResource): String = resource.value(this)
167