1 /*
2  * Copyright (C) 2019 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 android.content.om;
18 
19 import static android.annotation.SystemApi.Client.SYSTEM_SERVER;
20 
21 import static com.android.internal.util.Preconditions.checkNotNull;
22 
23 import android.annotation.IntDef;
24 import android.annotation.NonNull;
25 import android.annotation.Nullable;
26 import android.annotation.SuppressLint;
27 import android.annotation.SystemApi;
28 import android.os.Bundle;
29 import android.os.Parcel;
30 import android.os.Parcelable;
31 import android.os.UserHandle;
32 
33 import java.lang.annotation.Retention;
34 import java.lang.annotation.RetentionPolicy;
35 import java.util.ArrayList;
36 import java.util.Iterator;
37 import java.util.List;
38 import java.util.Locale;
39 import java.util.Objects;
40 
41 /**
42  * A container for a batch of requests to the OverlayManager.
43  *
44  * <p>An app can get an {@link OverlayManagerTransaction} with the specified {@link OverlayManager}
45  * to handle the transaction. The app can register multiple overlays and unregister multiple
46  * registered overlays in one transaction commitment.
47  *
48  * <p>The below example is registering a {@code updatingOverlay} and unregistering a {@code
49  * deprecatedOverlay} in one transaction commitment.
50  *
51  * <pre>{@code
52  * final OverlayManager overlayManager = ctx.getSystemService(OverlayManager.class);
53  * final OverlayManagerTransaction transaction = new OverlayManagerTransaction(overlayManager);
54  * transaction.registerFabricatedOverlay(updatingOverlay);
55  * transaction.unregisterFabricatedOverlay(deprecatedOverlay)
56  * transaction.commit();
57  * }</pre>
58  *
59  * @see OverlayManager
60  * @see FabricatedOverlay
61  */
62 public final class OverlayManagerTransaction implements Parcelable {
63     // TODO: remove @hide from this class when OverlayManager is added to the
64     // SDK, but keep OverlayManagerTransaction.Request @hidden
65     private final List<Request> mRequests;
66     private final boolean mSelfTargeting;
67 
68     /**
69      * Container for a batch of requests to the OverlayManagerService.
70      *
71      * <p>Transactions are created using a builder interface. Example usage:
72      * <pre>{@code
73      * final OverlayManager om = ctx.getSystemService(OverlayManager.class);
74      * final OverlayManagerTransaction t = new OverlayManagerTransaction.Builder()
75      *     .setEnabled(...)
76      *     .setEnabled(...)
77      *     .build();
78      * om.commit(t);
79      * }</pre>
80      */
OverlayManagerTransaction( @onNull final List<Request> requests, boolean selfTargeting)81     private OverlayManagerTransaction(
82             @NonNull final List<Request> requests, boolean selfTargeting) {
83         Objects.requireNonNull(requests);
84         if (requests.contains(null)) {
85             throw new IllegalArgumentException("null request");
86         }
87         mRequests = requests;
88         mSelfTargeting = selfTargeting;
89     }
90 
91     /**
92      * Get an overlay manager transaction.
93      *
94      * @return a new {@link OverlayManagerTransaction} instance.
95      */
96     @NonNull
newInstance()97     public static OverlayManagerTransaction newInstance() {
98         return new OverlayManagerTransaction(new ArrayList<>(), true /* selfTargeting */);
99     }
100 
OverlayManagerTransaction(@onNull final Parcel source)101     private OverlayManagerTransaction(@NonNull final Parcel source) {
102         final int size = source.readInt();
103         mRequests = new ArrayList<>(size);
104         for (int i = 0; i < size; i++) {
105             final int request = source.readInt();
106             final OverlayIdentifier overlay = source.readParcelable(null, android.content.om.OverlayIdentifier.class);
107             final int userId = source.readInt();
108             final Bundle extras = source.readBundle(null);
109             mRequests.add(new Request(request, overlay, userId, extras));
110         }
111         mSelfTargeting = false;
112     }
113 
114     /**
115      * Get the iterator of requests
116      *
117      * @return the iterator of request
118      * @hide
119      */
120     @SuppressLint("ReferencesHidden")
121     @NonNull
122     @SystemApi(client = SYSTEM_SERVER)
getRequests()123     public Iterator<Request> getRequests() {
124         return mRequests.iterator();
125     }
126 
127     /**
128      * {@inheritDoc}
129      *
130      * @hide
131      */
132     @Override
toString()133     public String toString() {
134         return String.format("OverlayManagerTransaction { mRequests = %s }", mRequests);
135     }
136 
137     /**
138      * A single unit of the transaction, such as a request to enable an
139      * overlay, or to disable an overlay.
140      *
141      * @hide
142      */
143     @SystemApi(client = SYSTEM_SERVER)
144     public static final class Request {
145         @IntDef(prefix = "TYPE_", value = {
146                 TYPE_SET_ENABLED,
147                 TYPE_SET_DISABLED,
148         })
149         @Retention(RetentionPolicy.SOURCE)
150         @interface RequestType {}
151 
152         public static final int TYPE_SET_ENABLED = 0;
153         public static final int TYPE_SET_DISABLED = 1;
154         public static final int TYPE_REGISTER_FABRICATED = 2;
155         public static final int TYPE_UNREGISTER_FABRICATED = 3;
156 
157         public static final String BUNDLE_FABRICATED_OVERLAY = "fabricated_overlay";
158 
159         @RequestType
160         public final int type;
161         @NonNull
162         public final OverlayIdentifier overlay;
163         public final int userId;
164 
165         @SuppressLint("NullableCollection")
166         @Nullable
167         public final Bundle extras;
168 
Request(@equestType final int type, @NonNull final OverlayIdentifier overlay, final int userId)169         public Request(@RequestType final int type, @NonNull final OverlayIdentifier overlay,
170                 final int userId) {
171             this(type, overlay, userId, null /* extras */);
172         }
173 
Request(@equestType final int type, @NonNull final OverlayIdentifier overlay, final int userId, @Nullable Bundle extras)174         public Request(@RequestType final int type, @NonNull final OverlayIdentifier overlay,
175                 final int userId, @Nullable Bundle extras) {
176             this.type = type;
177             this.overlay = overlay;
178             this.userId = userId;
179             this.extras = extras;
180         }
181 
182         @Override
toString()183         public String toString() {
184             return String.format(Locale.US, "Request{type=0x%02x (%s), overlay=%s, userId=%d}",
185                     type, typeToString(), overlay, userId);
186         }
187 
188         /**
189          * Translate the request type into a human readable string. Only
190          * intended for debugging.
191          *
192          * @hide
193          */
typeToString()194         public String typeToString() {
195             switch (type) {
196                 case TYPE_SET_ENABLED: return "TYPE_SET_ENABLED";
197                 case TYPE_SET_DISABLED: return "TYPE_SET_DISABLED";
198                 case TYPE_REGISTER_FABRICATED: return "TYPE_REGISTER_FABRICATED";
199                 case TYPE_UNREGISTER_FABRICATED: return "TYPE_UNREGISTER_FABRICATED";
200                 default: return String.format("TYPE_UNKNOWN (0x%02x)", type);
201             }
202         }
203     }
204 
205     /**
206      * Builder class for OverlayManagerTransaction objects.
207      * TODO(b/269197647): mark the API used by the systemUI.
208      * @hide
209      */
210     public static final class Builder {
211         private final List<Request> mRequests = new ArrayList<>();
212 
213         /**
214          * Request that an overlay package be enabled and change its loading
215          * order to the last package to be loaded, or disabled
216          *
217          * If the caller has the correct permissions, it is always possible to
218          * disable an overlay. Due to technical and security reasons it may not
219          * always be possible to enable an overlay, for instance if the overlay
220          * does not successfully overlay any target resources due to
221          * overlayable policy restrictions.
222          *
223          * An enabled overlay is a part of target package's resources, i.e. it will
224          * be part of any lookups performed via {@link android.content.res.Resources}
225          * and {@link android.content.res.AssetManager}. A disabled overlay will no
226          * longer affect the resources of the target package. If the target is
227          * currently running, its outdated resources will be replaced by new ones.
228          *
229          * @param overlay The name of the overlay package.
230          * @param enable true to enable the overlay, false to disable it.
231          * @return this Builder object, so you can chain additional requests
232          */
setEnabled(@onNull OverlayIdentifier overlay, boolean enable)233         public Builder setEnabled(@NonNull OverlayIdentifier overlay, boolean enable) {
234             return setEnabled(overlay, enable, UserHandle.myUserId());
235         }
236 
237         /**
238          * @hide
239          */
setEnabled(@onNull OverlayIdentifier overlay, boolean enable, int userId)240         public Builder setEnabled(@NonNull OverlayIdentifier overlay, boolean enable, int userId) {
241             checkNotNull(overlay);
242             @Request.RequestType final int type =
243                 enable ? Request.TYPE_SET_ENABLED : Request.TYPE_SET_DISABLED;
244             mRequests.add(new Request(type, overlay, userId));
245             return this;
246         }
247 
248         /**
249          * Registers the fabricated overlay with the overlay manager so it can be enabled and
250          * disabled for any user.
251          *
252          * The fabricated overlay is initialized in a disabled state. If an overlay is re-registered
253          * the existing overlay will be replaced by the newly registered overlay and the enabled
254          * state of the overlay will be left unchanged if the target package and target overlayable
255          * have not changed.
256          *
257          * @param overlay the overlay to register with the overlay manager
258          *
259          * @hide
260          */
261         @NonNull
registerFabricatedOverlay(@onNull FabricatedOverlay overlay)262         public Builder registerFabricatedOverlay(@NonNull FabricatedOverlay overlay) {
263             mRequests.add(generateRegisterFabricatedOverlayRequest(overlay));
264             return this;
265         }
266 
267         /**
268          * Disables and removes the overlay from the overlay manager for all users.
269          *
270          * @param overlay the overlay to disable and remove
271          *
272          * @hide
273          */
274         @NonNull
unregisterFabricatedOverlay(@onNull OverlayIdentifier overlay)275         public Builder unregisterFabricatedOverlay(@NonNull OverlayIdentifier overlay) {
276             mRequests.add(generateUnRegisterFabricatedOverlayRequest(overlay));
277             return this;
278         }
279 
280         /**
281          * Create a new transaction out of the requests added so far. Execute
282          * the transaction by calling OverlayManager#commit.
283          *
284          * @see OverlayManager#commit
285          * @return a new transaction
286          */
287         @NonNull
build()288         public OverlayManagerTransaction build() {
289             return new OverlayManagerTransaction(mRequests, false /* selfTargeting */);
290         }
291     }
292 
293     /**
294      * {@inheritDoc}
295      */
296     @Override
describeContents()297     public int describeContents() {
298         return 0;
299     }
300 
301     /**
302      * {@inheritDoc}
303      */
304     @Override
writeToParcel(@onNull Parcel dest, int flags)305     public void writeToParcel(@NonNull Parcel dest, int flags) {
306         final int size = mRequests.size();
307         dest.writeInt(size);
308         for (int i = 0; i < size; i++) {
309             final Request req = mRequests.get(i);
310             dest.writeInt(req.type);
311             dest.writeParcelable(req.overlay, flags);
312             dest.writeInt(req.userId);
313             dest.writeBundle(req.extras);
314         }
315     }
316 
317     @NonNull
318     public static final Parcelable.Creator<OverlayManagerTransaction> CREATOR =
319             new Parcelable.Creator<OverlayManagerTransaction>() {
320 
321         @Override
322         public OverlayManagerTransaction createFromParcel(Parcel source) {
323             return new OverlayManagerTransaction(source);
324         }
325 
326         @Override
327         public OverlayManagerTransaction[] newArray(int size) {
328             return new OverlayManagerTransaction[size];
329         }
330     };
331 
generateRegisterFabricatedOverlayRequest( @onNull FabricatedOverlay overlay)332     private static Request generateRegisterFabricatedOverlayRequest(
333             @NonNull FabricatedOverlay overlay) {
334         Objects.requireNonNull(overlay);
335 
336         final Bundle extras = new Bundle();
337         extras.putParcelable(Request.BUNDLE_FABRICATED_OVERLAY, overlay.mOverlay);
338         return new Request(Request.TYPE_REGISTER_FABRICATED, overlay.getIdentifier(),
339                 UserHandle.USER_ALL, extras);
340     }
341 
generateUnRegisterFabricatedOverlayRequest( @onNull OverlayIdentifier overlayIdentifier)342     private static Request generateUnRegisterFabricatedOverlayRequest(
343             @NonNull OverlayIdentifier overlayIdentifier) {
344         Objects.requireNonNull(overlayIdentifier);
345 
346         return new Request(Request.TYPE_UNREGISTER_FABRICATED, overlayIdentifier,
347                 UserHandle.USER_ALL);
348     }
349 
350     /**
351      * Registers the fabricated overlays with the overlay manager so it can be used to overlay
352      * the app resources in runtime.
353      *
354      * <p>If an overlay is re-registered the existing overlay will be replaced by the newly
355      * registered overlay. The registered overlay will be left unchanged until the target
356      * package or target overlayable is changed.
357      *
358      * @param overlay the overlay to register with the overlay manager
359      */
360     @NonNull
registerFabricatedOverlay(@onNull FabricatedOverlay overlay)361     public void registerFabricatedOverlay(@NonNull FabricatedOverlay overlay) {
362         mRequests.add(generateRegisterFabricatedOverlayRequest(overlay));
363     }
364 
365     /**
366      * Unregisters the registered overlays from the overlay manager.
367      *
368      * @param overlay the overlay to be unregistered
369      *
370      * @see OverlayManager#getOverlayInfosForTarget(String)
371      * @see OverlayInfo#getOverlayIdentifier()
372      */
373     @NonNull
unregisterFabricatedOverlay(@onNull OverlayIdentifier overlay)374     public void unregisterFabricatedOverlay(@NonNull OverlayIdentifier overlay) {
375         mRequests.add(generateUnRegisterFabricatedOverlayRequest(overlay));
376     }
377 
378     /**
379      * Indicate whether the transaction is for self-targeting or not.
380      */
isSelfTargeting()381     boolean isSelfTargeting() {
382         return mSelfTargeting;
383     }
384 }
385