• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 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 
17 package com.android.server.om;
18 
19 import android.annotation.NonNull;
20 import android.annotation.Nullable;
21 import android.content.Context;
22 import android.content.om.FabricatedOverlay;
23 import android.content.om.IOverlayManager;
24 import android.content.om.OverlayIdentifier;
25 import android.content.om.OverlayInfo;
26 import android.content.om.OverlayManagerTransaction;
27 import android.content.pm.PackageManager;
28 import android.content.res.AssetManager;
29 import android.content.res.Resources;
30 import android.content.res.TypedArray;
31 import android.os.Binder;
32 import android.os.Process;
33 import android.os.RemoteException;
34 import android.os.ShellCommand;
35 import android.os.UserHandle;
36 import android.util.TypedValue;
37 
38 import java.io.PrintWriter;
39 import java.util.List;
40 import java.util.Map;
41 import java.util.regex.Matcher;
42 import java.util.regex.Pattern;
43 
44 /**
45  * Implementation of 'cmd overlay' commands.
46  *
47  * This class provides an interface to the OverlayManagerService via adb.
48  * Intended only for manual debugging. Execute 'adb exec-out cmd overlay help'
49  * for a list of available commands.
50  */
51 final class OverlayManagerShellCommand extends ShellCommand {
52     private final Context mContext;
53     private final IOverlayManager mInterface;
54 
OverlayManagerShellCommand(@onNull final Context ctx, @NonNull final IOverlayManager iom)55     OverlayManagerShellCommand(@NonNull final Context ctx, @NonNull final IOverlayManager iom) {
56         mContext = ctx;
57         mInterface = iom;
58     }
59 
60     @Override
onCommand(@ullable final String cmd)61     public int onCommand(@Nullable final String cmd) {
62         if (cmd == null) {
63             return handleDefaultCommands(cmd);
64         }
65         final PrintWriter err = getErrPrintWriter();
66         try {
67             switch (cmd) {
68                 case "list":
69                     return runList();
70                 case "enable":
71                     return runEnableDisable(true);
72                 case "disable":
73                     return runEnableDisable(false);
74                 case "enable-exclusive":
75                     return runEnableExclusive();
76                 case "set-priority":
77                     return runSetPriority();
78                 case "lookup":
79                     return runLookup();
80                 case "fabricate":
81                     return runFabricate();
82                 default:
83                     return handleDefaultCommands(cmd);
84             }
85         } catch (IllegalArgumentException e) {
86             err.println("Error: " + e.getMessage());
87         } catch (RemoteException e) {
88             err.println("Remote exception: " + e);
89         }
90         return -1;
91     }
92 
93     @Override
onHelp()94     public void onHelp() {
95         final PrintWriter out = getOutPrintWriter();
96         out.println("Overlay manager (overlay) commands:");
97         out.println("  help");
98         out.println("    Print this help text.");
99         out.println("  dump [--verbose] [--user USER_ID] [[FIELD] PACKAGE[:NAME]]");
100         out.println("    Print debugging information about the overlay manager.");
101         out.println("    With optional parameters PACKAGE and NAME, limit output to the specified");
102         out.println("    overlay or target. With optional parameter FIELD, limit output to");
103         out.println("    the corresponding SettingsItem field. Field names are all lower case");
104         out.println("    and omit the m prefix, i.e. 'userid' for SettingsItem.mUserId.");
105         out.println("  list [--user USER_ID] [PACKAGE[:NAME]]");
106         out.println("    Print information about target and overlay packages.");
107         out.println("    Overlay packages are printed in priority order. With optional");
108         out.println("    parameters PACKAGE and NAME, limit output to the specified overlay or");
109         out.println("    target.");
110         out.println("  enable [--user USER_ID] PACKAGE[:NAME]");
111         out.println("    Enable overlay within or owned by PACKAGE with optional unique NAME.");
112         out.println("  disable [--user USER_ID] PACKAGE[:NAME]");
113         out.println("    Disable overlay within or owned by PACKAGE with optional unique NAME.");
114         out.println("  enable-exclusive [--user USER_ID] [--category] PACKAGE");
115         out.println("    Enable overlay within or owned by PACKAGE and disable all other overlays");
116         out.println("    for its target package. If the --category option is given, only disables");
117         out.println("    other overlays in the same category.");
118         out.println("  set-priority [--user USER_ID] PACKAGE PARENT|lowest|highest");
119         out.println("    Change the priority of the overlay to be just higher than");
120         out.println("    the priority of PARENT If PARENT is the special keyword");
121         out.println("    'lowest', change priority of PACKAGE to the lowest priority.");
122         out.println("    If PARENT is the special keyword 'highest', change priority of");
123         out.println("    PACKAGE to the highest priority.");
124         out.println("  lookup [--user USER_ID] [--verbose] PACKAGE-TO-LOAD PACKAGE:TYPE/NAME");
125         out.println("    Load a package and print the value of a given resource");
126         out.println("    applying the current configuration and enabled overlays.");
127         out.println("    For a more fine-grained alternative, use 'idmap2 lookup'.");
128         out.println("  fabricate [--user USER_ID] [--target-name OVERLAYABLE] --target PACKAGE");
129         out.println("            --name NAME PACKAGE:TYPE/NAME ENCODED-TYPE-ID ENCODED-VALUE");
130         out.println("    Create an overlay from a single resource. Caller must be root. Example:");
131         out.println("      fabricate --target android --name LighterGray \\");
132         out.println("                android:color/lighter_gray 0x1c 0xffeeeeee");
133     }
134 
runList()135     private int runList() throws RemoteException {
136         final PrintWriter out = getOutPrintWriter();
137         final PrintWriter err = getErrPrintWriter();
138 
139         int userId = UserHandle.USER_SYSTEM;
140         String opt;
141         while ((opt = getNextOption()) != null) {
142             switch (opt) {
143                 case "--user":
144                     userId = UserHandle.parseUserArg(getNextArgRequired());
145                     break;
146                 default:
147                     err.println("Error: Unknown option: " + opt);
148                     return 1;
149             }
150         }
151 
152         final String packageName = getNextArg();
153         if (packageName != null) {
154             List<OverlayInfo> overlaysForTarget = mInterface.getOverlayInfosForTarget(
155                     packageName, userId);
156 
157             // If the package is not targeted by any overlays, check if the package is an overlay.
158             if (overlaysForTarget.isEmpty()) {
159                 final OverlayInfo info = mInterface.getOverlayInfo(packageName, userId);
160                 if (info != null) {
161                     printListOverlay(out, info);
162                 }
163                 return 0;
164             }
165 
166             out.println(packageName);
167 
168             // Print the overlays for the target.
169             final int n = overlaysForTarget.size();
170             for (int i = 0; i < n; i++) {
171                 printListOverlay(out, overlaysForTarget.get(i));
172             }
173 
174             return 0;
175         }
176 
177         // Print all overlays grouped by target package name.
178         final Map<String, List<OverlayInfo>> allOverlays = mInterface.getAllOverlays(userId);
179         for (final String targetPackageName : allOverlays.keySet()) {
180             out.println(targetPackageName);
181 
182             List<OverlayInfo> overlaysForTarget = allOverlays.get(targetPackageName);
183             final int n = overlaysForTarget.size();
184             for (int i = 0; i < n; i++) {
185                 printListOverlay(out, overlaysForTarget.get(i));
186             }
187             out.println();
188         }
189 
190         return 0;
191     }
192 
printListOverlay(PrintWriter out, OverlayInfo oi)193     private void printListOverlay(PrintWriter out, OverlayInfo oi) {
194         String status;
195         switch (oi.state) {
196             case OverlayInfo.STATE_ENABLED_IMMUTABLE:
197             case OverlayInfo.STATE_ENABLED:
198                 status = "[x]";
199                 break;
200             case OverlayInfo.STATE_DISABLED:
201                 status = "[ ]";
202                 break;
203             default:
204                 status = "---";
205                 break;
206         }
207         out.println(String.format("%s %s", status, oi.getOverlayIdentifier()));
208     }
209 
runEnableDisable(final boolean enable)210     private int runEnableDisable(final boolean enable) throws RemoteException {
211         final PrintWriter err = getErrPrintWriter();
212 
213         int userId = UserHandle.USER_SYSTEM;
214         String opt;
215         while ((opt = getNextOption()) != null) {
216             switch (opt) {
217                 case "--user":
218                     userId = UserHandle.parseUserArg(getNextArgRequired());
219                     break;
220                 default:
221                     err.println("Error: Unknown option: " + opt);
222                     return 1;
223             }
224         }
225 
226         final OverlayIdentifier overlay = OverlayIdentifier.fromString(getNextArgRequired());
227         mInterface.commit(new OverlayManagerTransaction.Builder()
228                 .setEnabled(overlay, enable, userId)
229                 .build());
230         return 0;
231     }
232 
runFabricate()233     private int runFabricate() throws RemoteException {
234         final PrintWriter err = getErrPrintWriter();
235         if (Binder.getCallingUid() != Process.ROOT_UID) {
236             err.println("Error: must be root to fabricate overlays through the shell");
237             return 1;
238         }
239 
240         int userId = UserHandle.USER_SYSTEM;
241         String targetPackage = "";
242         String targetOverlayable = "";
243         String name = "";
244         String opt;
245         while ((opt = getNextOption()) != null) {
246             switch (opt) {
247                 case "--user":
248                     userId = UserHandle.parseUserArg(getNextArgRequired());
249                     break;
250                 case "--target":
251                     targetPackage = getNextArgRequired();
252                     break;
253                 case "--target-name":
254                     targetOverlayable = getNextArgRequired();
255                     break;
256                 case "--name":
257                     name = getNextArgRequired();
258                     break;
259                 default:
260                     err.println("Error: Unknown option: " + opt);
261                     return 1;
262             }
263         }
264 
265         if (name.isEmpty()) {
266             err.println("Error: Missing required arg '--name'");
267             return 1;
268         }
269 
270         if (targetPackage.isEmpty()) {
271             err.println("Error: Missing required arg '--target'");
272             return 1;
273         }
274 
275         final String resourceName = getNextArgRequired();
276         final String typeStr = getNextArgRequired();
277         final int type;
278         if (typeStr.startsWith("0x")) {
279             type = Integer.parseUnsignedInt(typeStr.substring(2), 16);
280         } else {
281             type = Integer.parseUnsignedInt(typeStr);
282         }
283         final String dataStr = getNextArgRequired();
284         final int data;
285         if (dataStr.startsWith("0x")) {
286             data = Integer.parseUnsignedInt(dataStr.substring(2), 16);
287         } else {
288             data = Integer.parseUnsignedInt(dataStr);
289         }
290 
291         final PackageManager pm = mContext.getPackageManager();
292         if (pm == null) {
293             err.println("Error: failed to get package manager");
294             return 1;
295         }
296 
297         final String overlayPackageName = "com.android.shell";
298         final FabricatedOverlay overlay = new FabricatedOverlay.Builder(
299                 overlayPackageName, name, targetPackage)
300                 .setTargetOverlayable(targetOverlayable)
301                 .setResourceValue(resourceName, type, data)
302                 .build();
303 
304         mInterface.commit(new OverlayManagerTransaction.Builder()
305                 .registerFabricatedOverlay(overlay)
306                 .build());
307         return 0;
308     }
309 
runEnableExclusive()310     private int runEnableExclusive() throws RemoteException {
311         final PrintWriter err = getErrPrintWriter();
312 
313         int userId = UserHandle.USER_SYSTEM;
314         boolean inCategory = false;
315         String opt;
316         while ((opt = getNextOption()) != null) {
317             switch (opt) {
318                 case "--user":
319                     userId = UserHandle.parseUserArg(getNextArgRequired());
320                     break;
321                 case "--category":
322                     inCategory = true;
323                     break;
324                 default:
325                     err.println("Error: Unknown option: " + opt);
326                     return 1;
327             }
328         }
329         final String overlay = getNextArgRequired();
330         if (inCategory) {
331             return mInterface.setEnabledExclusiveInCategory(overlay, userId) ? 0 : 1;
332         } else {
333             return mInterface.setEnabledExclusive(overlay, true, userId) ? 0 : 1;
334         }
335     }
336 
runSetPriority()337     private int runSetPriority() throws RemoteException {
338         final PrintWriter err = getErrPrintWriter();
339 
340         int userId = UserHandle.USER_SYSTEM;
341         String opt;
342         while ((opt = getNextOption()) != null) {
343             switch (opt) {
344                 case "--user":
345                     userId = UserHandle.parseUserArg(getNextArgRequired());
346                     break;
347                 default:
348                     err.println("Error: Unknown option: " + opt);
349                     return 1;
350             }
351         }
352 
353         final String packageName = getNextArgRequired();
354         final String newParentPackageName = getNextArgRequired();
355 
356         if ("highest".equals(newParentPackageName)) {
357             return mInterface.setHighestPriority(packageName, userId) ? 0 : 1;
358         } else if ("lowest".equals(newParentPackageName)) {
359             return mInterface.setLowestPriority(packageName, userId) ? 0 : 1;
360         } else {
361             return mInterface.setPriority(packageName, newParentPackageName, userId) ? 0 : 1;
362         }
363     }
364 
runLookup()365     private int runLookup() throws RemoteException {
366         final PrintWriter out = getOutPrintWriter();
367         final PrintWriter err = getErrPrintWriter();
368 
369         int userId = UserHandle.USER_SYSTEM;
370         boolean verbose = false;
371         String opt;
372         while ((opt = getNextOption()) != null) {
373             switch (opt) {
374                 case "--user":
375                     userId = UserHandle.parseUserArg(getNextArgRequired());
376                     break;
377                 case "--verbose":
378                     verbose = true;
379                     break;
380                 default:
381                     err.println("Error: Unknown option: " + opt);
382                     return 1;
383             }
384         }
385 
386         final String packageToLoad = getNextArgRequired();
387 
388         final String fullyQualifiedResourceName = getNextArgRequired(); // package:type/name
389         final Pattern regex = Pattern.compile("(.*?):(.*?)/(.*?)");
390         final Matcher matcher = regex.matcher(fullyQualifiedResourceName);
391         if (!matcher.matches()) {
392             err.println("Error: bad resource name, doesn't match package:type/name");
393             return 1;
394         }
395 
396         final Resources res;
397         try {
398             res = mContext
399                 .createContextAsUser(UserHandle.of(userId), /* flags */ 0)
400                 .getPackageManager()
401                 .getResourcesForApplication(packageToLoad);
402         } catch (PackageManager.NameNotFoundException e) {
403             err.println(String.format("Error: failed to get resources for package %s for user %d",
404                     packageToLoad, userId));
405             return 1;
406         }
407         final AssetManager assets = res.getAssets();
408         try {
409             assets.setResourceResolutionLoggingEnabled(true);
410 
411             // first try as non-complex type ...
412             try {
413                 final TypedValue value = new TypedValue();
414                 res.getValue(fullyQualifiedResourceName, value, false /* resolveRefs */);
415                 final CharSequence valueString = value.coerceToString();
416                 final String resolution = assets.getLastResourceResolution();
417 
418                 res.getValue(fullyQualifiedResourceName, value, true /* resolveRefs */);
419                 final CharSequence resolvedString = value.coerceToString();
420 
421                 if (verbose) {
422                     out.println(resolution);
423                 }
424 
425                 if (valueString.equals(resolvedString)) {
426                     out.println(valueString);
427                 } else {
428                     out.println(valueString + " -> " + resolvedString);
429                 }
430                 return 0;
431             } catch (Resources.NotFoundException e) {
432                 // this is ok, resource could still be a complex type
433             }
434 
435             // ... then try as complex type
436             try {
437 
438                 final String pkg = matcher.group(1);
439                 final String type = matcher.group(2);
440                 final String name = matcher.group(3);
441                 final int resid = res.getIdentifier(name, type, pkg);
442                 if (resid == 0) {
443                     throw new Resources.NotFoundException();
444                 }
445                 final TypedArray array = res.obtainTypedArray(resid);
446                 if (verbose) {
447                     out.println(assets.getLastResourceResolution());
448                 }
449                 TypedValue tv = new TypedValue();
450                 for (int i = 0; i < array.length(); i++) {
451                     array.getValue(i, tv);
452                     out.println(tv.coerceToString());
453                 }
454                 array.recycle();
455                 return 0;
456             } catch (Resources.NotFoundException e) {
457                 // give up
458                 err.println("Error: failed to get the resource " + fullyQualifiedResourceName);
459                 return 1;
460             }
461         } finally {
462             assets.setResourceResolutionLoggingEnabled(false);
463         }
464     }
465 }
466