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