• 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 static com.android.internal.content.om.OverlayConfig.PARTITION_ORDER_FILE_PATH;
20 import static com.android.server.om.OverlayManagerService.handleIncomingUser;
21 
22 import android.annotation.NonNull;
23 import android.annotation.Nullable;
24 import android.content.Context;
25 import android.content.om.FabricatedOverlay;
26 import android.content.om.IOverlayManager;
27 import android.content.om.OverlayIdentifier;
28 import android.content.om.OverlayInfo;
29 import android.content.om.OverlayManagerTransaction;
30 import android.content.pm.PackageManager;
31 import android.content.res.AssetManager;
32 import android.content.res.Resources;
33 import android.content.res.TypedArray;
34 import android.os.Binder;
35 import android.os.ParcelFileDescriptor;
36 import android.os.Process;
37 import android.os.RemoteException;
38 import android.os.ShellCommand;
39 import android.os.UserHandle;
40 import android.text.TextUtils;
41 import android.util.TypedValue;
42 import android.util.Xml;
43 
44 import com.android.modules.utils.TypedXmlPullParser;
45 
46 import org.xmlpull.v1.XmlPullParser;
47 import org.xmlpull.v1.XmlPullParserException;
48 
49 import java.io.File;
50 import java.io.FileInputStream;
51 import java.io.IOException;
52 import java.io.PrintWriter;
53 import java.util.List;
54 import java.util.Locale;
55 import java.util.Map;
56 import java.util.regex.Matcher;
57 import java.util.regex.Pattern;
58 
59 /**
60  * Implementation of 'cmd overlay' commands.
61  *
62  * This class provides an interface to the OverlayManagerService via adb.
63  * Intended only for manual debugging. Execute 'adb exec-out cmd overlay help'
64  * for a list of available commands.
65  */
66 final class OverlayManagerShellCommand extends ShellCommand {
67     private final Context mContext;
68     private final IOverlayManager mInterface;
69     private static final Map<String, Integer> TYPE_MAP = Map.of(
70             "color", TypedValue.TYPE_FIRST_COLOR_INT,
71             "string", TypedValue.TYPE_STRING,
72             "drawable", -1);
73 
OverlayManagerShellCommand(@onNull final Context ctx, @NonNull final IOverlayManager iom)74     OverlayManagerShellCommand(@NonNull final Context ctx, @NonNull final IOverlayManager iom) {
75         mContext = ctx;
76         mInterface = iom;
77     }
78 
79     @Override
onCommand(@ullable final String cmd)80     public int onCommand(@Nullable final String cmd) {
81         if (cmd == null) {
82             return handleDefaultCommands(cmd);
83         }
84         final PrintWriter err = getErrPrintWriter();
85         try {
86             switch (cmd) {
87                 case "list":
88                     return runList();
89                 case "enable":
90                     return runEnableDisable(true);
91                 case "disable":
92                     return runEnableDisable(false);
93                 case "enable-exclusive":
94                     return runEnableExclusive();
95                 case "set-priority":
96                     return runSetPriority();
97                 case "lookup":
98                     return runLookup();
99                 case "fabricate":
100                     return runFabricate();
101                 case "partition-order":
102                     return runPartitionOrder();
103                 default:
104                     return handleDefaultCommands(cmd);
105             }
106         } catch (IllegalArgumentException e) {
107             err.println("Error: " + e.getMessage());
108         } catch (RemoteException e) {
109             err.println("Remote exception: " + e);
110         }
111         return -1;
112     }
113 
114     @Override
onHelp()115     public void onHelp() {
116         final PrintWriter out = getOutPrintWriter();
117         out.println("Overlay manager (overlay) commands:");
118         out.println("  help");
119         out.println("    Print this help text.");
120         out.println("  dump [--verbose] [--user USER_ID] [[FIELD] PACKAGE[:NAME]]");
121         out.println("    Print debugging information about the overlay manager.");
122         out.println("    With optional parameters PACKAGE and NAME, limit output to the specified");
123         out.println("    overlay or target. With optional parameter FIELD, limit output to");
124         out.println("    the corresponding SettingsItem field. Field names are all lower case");
125         out.println("    and omit the m prefix, i.e. 'userid' for SettingsItem.mUserId.");
126         out.println("  list [--user USER_ID] [PACKAGE[:NAME]]");
127         out.println("    Print information about target and overlay packages.");
128         out.println("    Overlay packages are printed in priority order. With optional");
129         out.println("    parameters PACKAGE and NAME, limit output to the specified overlay or");
130         out.println("    target.");
131         out.println("  enable [--user USER_ID] PACKAGE[:NAME]");
132         out.println("    Enable overlay within or owned by PACKAGE with optional unique NAME.");
133         out.println("  disable [--user USER_ID] PACKAGE[:NAME]");
134         out.println("    Disable overlay within or owned by PACKAGE with optional unique NAME.");
135         out.println("  enable-exclusive [--user USER_ID] [--category] PACKAGE");
136         out.println("    Enable overlay within or owned by PACKAGE and disable all other overlays");
137         out.println("    for its target package. If the --category option is given, only disables");
138         out.println("    other overlays in the same category.");
139         out.println("  set-priority [--user USER_ID] PACKAGE PARENT|lowest|highest");
140         out.println("    Change the priority of the overlay to be just higher than");
141         out.println("    the priority of PARENT If PARENT is the special keyword");
142         out.println("    'lowest', change priority of PACKAGE to the lowest priority.");
143         out.println("    If PARENT is the special keyword 'highest', change priority of");
144         out.println("    PACKAGE to the highest priority.");
145         out.println("  lookup [--user USER_ID] [--verbose] PACKAGE-TO-LOAD PACKAGE:TYPE/NAME");
146         out.println("    Load a package and print the value of a given resource");
147         out.println("    applying the current configuration and enabled overlays.");
148         out.println("    For a more fine-grained alternative, use 'idmap2 lookup'.");
149         out.println("  fabricate [--target-name OVERLAYABLE] --target PACKAGE");
150         out.println("            --name NAME [--file FILE] ");
151         out.println("            PACKAGE:TYPE/NAME ENCODED-TYPE-ID|TYPE-NAME ENCODED-VALUE");
152         out.println("    Create an overlay from a single resource. Caller must be root. Example:");
153         out.println("      fabricate --target android --name LighterGray \\");
154         out.println("                android:color/lighter_gray 0x1c 0xffeeeeee");
155         out.println("  partition-order");
156         out.println("    Print the partition order from overlay config and how this order");
157         out.println("    got established, by default or by " + PARTITION_ORDER_FILE_PATH);
158     }
159 
runList()160     private int runList() throws RemoteException {
161         final PrintWriter out = getOutPrintWriter();
162         final PrintWriter err = getErrPrintWriter();
163 
164         int userId = UserHandle.USER_CURRENT;
165         String opt;
166         while ((opt = getNextOption()) != null) {
167             switch (opt) {
168                 case "--user":
169                     userId = UserHandle.parseUserArg(getNextArgRequired());
170                     break;
171                 default:
172                     err.println("Error: Unknown option: " + opt);
173                     return 1;
174             }
175         }
176 
177         final String packageName = getNextArg();
178         if (packageName != null) {
179             List<OverlayInfo> overlaysForTarget = mInterface.getOverlayInfosForTarget(
180                     packageName, userId);
181 
182             // If the package is not targeted by any overlays, check if the package is an overlay.
183             if (overlaysForTarget.isEmpty()) {
184                 final OverlayInfo info = mInterface.getOverlayInfo(packageName, userId);
185                 if (info != null) {
186                     printListOverlay(out, info);
187                 }
188                 return 0;
189             }
190 
191             out.println(packageName);
192 
193             // Print the overlays for the target.
194             final int n = overlaysForTarget.size();
195             for (int i = 0; i < n; i++) {
196                 printListOverlay(out, overlaysForTarget.get(i));
197             }
198 
199             return 0;
200         }
201 
202         // Print all overlays grouped by target package name.
203         final Map<String, List<OverlayInfo>> allOverlays = mInterface.getAllOverlays(userId);
204         for (final String targetPackageName : allOverlays.keySet()) {
205             out.println(targetPackageName);
206 
207             List<OverlayInfo> overlaysForTarget = allOverlays.get(targetPackageName);
208             final int n = overlaysForTarget.size();
209             for (int i = 0; i < n; i++) {
210                 printListOverlay(out, overlaysForTarget.get(i));
211             }
212             out.println();
213         }
214 
215         return 0;
216     }
217 
printListOverlay(PrintWriter out, OverlayInfo oi)218     private void printListOverlay(PrintWriter out, OverlayInfo oi) {
219         String status;
220         switch (oi.state) {
221             case OverlayInfo.STATE_ENABLED_IMMUTABLE:
222             case OverlayInfo.STATE_ENABLED:
223                 status = "[x]";
224                 break;
225             case OverlayInfo.STATE_DISABLED:
226                 status = "[ ]";
227                 break;
228             default:
229                 status = "---";
230                 break;
231         }
232         out.println(String.format("%s %s", status, oi.getOverlayIdentifier()));
233     }
234 
runEnableDisable(final boolean enable)235     private int runEnableDisable(final boolean enable) throws RemoteException {
236         final PrintWriter err = getErrPrintWriter();
237 
238         int userId = UserHandle.USER_CURRENT;
239         String opt;
240         while ((opt = getNextOption()) != null) {
241             switch (opt) {
242                 case "--user":
243                     userId = UserHandle.parseUserArg(getNextArgRequired());
244                     break;
245                 default:
246                     err.println("Error: Unknown option: " + opt);
247                     return 1;
248             }
249         }
250 
251         final OverlayIdentifier overlay = OverlayIdentifier.fromString(getNextArgRequired());
252         mInterface.commit(new OverlayManagerTransaction.Builder()
253                 .setEnabled(overlay, enable, userId)
254                 .build());
255         return 0;
256     }
257 
runPartitionOrder()258     private int runPartitionOrder() throws RemoteException {
259         final PrintWriter out = getOutPrintWriter();
260         out.println("Partition order (low to high priority): " + mInterface.getPartitionOrder());
261         out.println("Established by " + (mInterface.isDefaultPartitionOrder() ? "default"
262                 : PARTITION_ORDER_FILE_PATH));
263         return 0;
264     }
265 
runFabricate()266     private int runFabricate() throws RemoteException {
267         final PrintWriter err = getErrPrintWriter();
268         if (Binder.getCallingUid() != Process.ROOT_UID) {
269             err.println("Error: must be root to fabricate overlays through the shell");
270             return 1;
271         }
272 
273         String targetPackage = "";
274         String targetOverlayable = "";
275         String name = "";
276         String filename = null;
277         String opt;
278         String config = null;
279         while ((opt = getNextOption()) != null) {
280             switch (opt) {
281                 case "--target":
282                     targetPackage = getNextArgRequired();
283                     break;
284                 case "--target-name":
285                     targetOverlayable = getNextArgRequired();
286                     break;
287                 case "--name":
288                     name = getNextArgRequired();
289                     break;
290                 case "--file":
291                     filename = getNextArgRequired();
292                     break;
293                 case "--config":
294                     config = getNextArgRequired();
295                     break;
296                 default:
297                     err.println("Error: Unknown option: " + opt);
298                     return 1;
299             }
300         }
301 
302         if (name.isEmpty()) {
303             err.println("Error: Missing required arg '--name'");
304             return 1;
305         }
306 
307         if (targetPackage.isEmpty()) {
308             err.println("Error: Missing required arg '--target'");
309             return 1;
310         }
311         if (filename != null && getRemainingArgsCount() > 0) {
312             err.println(
313                     "Error: When passing --file don't pass resource name, type, and value as well");
314             return 1;
315         }
316         final String overlayPackageName = "com.android.shell";
317         FabricatedOverlay overlay = new FabricatedOverlay(name, targetPackage);
318         overlay.setTargetOverlayable(targetOverlayable);
319         overlay.setOwningPackage(overlayPackageName);
320         if (filename != null) {
321             int result = addOverlayValuesFromXml(overlay, targetPackage, filename);
322             if (result != 0) {
323                 return result;
324             }
325         } else {
326             final String resourceName = getNextArgRequired();
327             final String typeStr = getNextArgRequired();
328             final String strData = String.join(" ", peekRemainingArgs());
329             if (addOverlayValue(overlay, resourceName, typeStr, strData, config) != 0) {
330                 return 1;
331             }
332         }
333 
334         mInterface.commit(new OverlayManagerTransaction.Builder()
335                 .registerFabricatedOverlay(overlay).build());
336         return 0;
337     }
338 
addOverlayValuesFromXml( FabricatedOverlay overlay, String targetPackage, String filename)339     private int addOverlayValuesFromXml(
340             FabricatedOverlay overlay, String targetPackage, String filename) {
341         final PrintWriter err = getErrPrintWriter();
342         File file = new File(filename);
343         if (!file.exists()) {
344             err.println("Error: File does not exist");
345             return 1;
346         }
347         if (!file.canRead()) {
348             err.println("Error: File is unreadable");
349             return 1;
350         }
351         try (FileInputStream fis = new FileInputStream(file)) {
352             TypedXmlPullParser parser = Xml.resolvePullParser(fis);
353             int type;
354             while ((type = parser.next()) != XmlPullParser.START_TAG
355                     && type != XmlPullParser.END_DOCUMENT) {
356                 continue;
357             }
358             parser.require(XmlPullParser.START_TAG, null, "overlay");
359             while ((type = parser.next()) != XmlPullParser.END_DOCUMENT) {
360                 if (type == XmlPullParser.START_TAG) {
361                     String tagName = parser.getName();
362                     if (!tagName.equals("item")) {
363                         err.println(TextUtils.formatSimple("Error: Unexpected tag: %s at line %d",
364                                 tagName, parser.getLineNumber()));
365                     } else if (!parser.isEmptyElementTag()) {
366                         err.println("Error: item tag must be empty");
367                         return 1;
368                     } else {
369                         String target = parser.getAttributeValue(null, "target");
370                         if (TextUtils.isEmpty(target)) {
371                             err.println(
372                                     "Error: target name missing at line " + parser.getLineNumber());
373                             return 1;
374                         }
375                         int index = target.indexOf('/');
376                         if (index < 0) {
377                             err.println("Error: target malformed, missing '/' at line "
378                                     + parser.getLineNumber());
379                             return 1;
380                         }
381                         String overlayType = target.substring(0, index);
382                         String value = parser.getAttributeValue(null, "value");
383                         if (TextUtils.isEmpty(value)) {
384                             err.println("Error: value missing at line " + parser.getLineNumber());
385                             return 1;
386                         }
387                         String config = parser.getAttributeValue(null, "config");
388                         if (addOverlayValue(overlay, targetPackage + ':' + target,
389                                   overlayType, value, config) != 0) {
390                             return 1;
391                         }
392                     }
393                 }
394             }
395         } catch (IOException e) {
396             e.printStackTrace();
397             return 1;
398         } catch (XmlPullParserException e) {
399             e.printStackTrace();
400             return 1;
401         }
402         return 0;
403     }
404 
addOverlayValue(FabricatedOverlay overlay, String resourceName, String typeString, String valueString, String configuration)405     private int addOverlayValue(FabricatedOverlay overlay, String resourceName, String typeString,
406                                 String valueString, String configuration) {
407         final int type;
408         typeString = typeString.toLowerCase(Locale.getDefault());
409         if (TYPE_MAP.containsKey(typeString)) {
410             type = TYPE_MAP.get(typeString);
411         } else {
412             if (typeString.startsWith("0x")) {
413                 type = Integer.parseUnsignedInt(typeString.substring(2), 16);
414             } else {
415                 type = Integer.parseUnsignedInt(typeString);
416             }
417         }
418         if (type == TypedValue.TYPE_STRING) {
419             overlay.setResourceValue(resourceName, type, valueString, configuration);
420         } else if (type < 0) {
421             ParcelFileDescriptor pfd =  openFileForSystem(valueString, "r");
422             if (valueString.endsWith(".9.png")) {
423                 overlay.setNinePatchResourceValue(resourceName, pfd, configuration);
424             } else {
425                 overlay.setResourceValue(resourceName, pfd, configuration);
426             }
427         } else {
428             final int intData;
429             if (valueString.startsWith("0x")) {
430                 intData = Integer.parseUnsignedInt(valueString.substring(2), 16);
431             } else {
432                 intData = Integer.parseUnsignedInt(valueString);
433             }
434             overlay.setResourceValue(resourceName, type, intData, configuration);
435         }
436         return 0;
437     }
438 
runEnableExclusive()439     private int runEnableExclusive() throws RemoteException {
440         final PrintWriter err = getErrPrintWriter();
441 
442         int userId = UserHandle.USER_CURRENT;
443         boolean inCategory = false;
444         String opt;
445         while ((opt = getNextOption()) != null) {
446             switch (opt) {
447                 case "--user":
448                     userId = UserHandle.parseUserArg(getNextArgRequired());
449                     break;
450                 case "--category":
451                     inCategory = true;
452                     break;
453                 default:
454                     err.println("Error: Unknown option: " + opt);
455                     return 1;
456             }
457         }
458         final String overlay = getNextArgRequired();
459         if (inCategory) {
460             return mInterface.setEnabledExclusiveInCategory(overlay, userId) ? 0 : 1;
461         } else {
462             return mInterface.setEnabledExclusive(overlay, true, userId) ? 0 : 1;
463         }
464     }
465 
runSetPriority()466     private int runSetPriority() throws RemoteException {
467         final PrintWriter err = getErrPrintWriter();
468 
469         int userId = UserHandle.USER_CURRENT;
470         String opt;
471         while ((opt = getNextOption()) != null) {
472             switch (opt) {
473                 case "--user":
474                     userId = UserHandle.parseUserArg(getNextArgRequired());
475                     break;
476                 default:
477                     err.println("Error: Unknown option: " + opt);
478                     return 1;
479             }
480         }
481 
482         final String packageName = getNextArgRequired();
483         final String newParentPackageName = getNextArgRequired();
484 
485         if ("highest".equals(newParentPackageName)) {
486             return mInterface.setHighestPriority(packageName, userId) ? 0 : 1;
487         } else if ("lowest".equals(newParentPackageName)) {
488             return mInterface.setLowestPriority(packageName, userId) ? 0 : 1;
489         } else {
490             return mInterface.setPriority(packageName, newParentPackageName, userId) ? 0 : 1;
491         }
492     }
493 
runLookup()494     private int runLookup() throws RemoteException {
495         final PrintWriter out = getOutPrintWriter();
496         final PrintWriter err = getErrPrintWriter();
497 
498         int userId = UserHandle.USER_CURRENT;
499         boolean verbose = false;
500         String opt;
501         while ((opt = getNextOption()) != null) {
502             switch (opt) {
503                 case "--user":
504                     userId = UserHandle.parseUserArg(getNextArgRequired());
505                     break;
506                 case "--verbose":
507                     verbose = true;
508                     break;
509                 default:
510                     err.println("Error: Unknown option: " + opt);
511                     return 1;
512             }
513         }
514 
515         final String packageToLoad = getNextArgRequired();
516 
517         final String fullyQualifiedResourceName = getNextArgRequired(); // package:type/name
518         final Pattern regex = Pattern.compile("(.*?):(.*?)/(.*?)");
519         final Matcher matcher = regex.matcher(fullyQualifiedResourceName);
520         if (!matcher.matches()) {
521             err.println("Error: bad resource name, doesn't match package:type/name");
522             return 1;
523         }
524 
525         final int realUserId = handleIncomingUser(userId, "runLookup");
526         final Resources res;
527         try {
528             res = mContext
529                 .createContextAsUser(UserHandle.of(realUserId), /* flags */ 0)
530                 .getPackageManager()
531                 .getResourcesForApplication(packageToLoad);
532         } catch (PackageManager.NameNotFoundException e) {
533             err.println(String.format("Error: failed to get resources for package %s for user %d",
534                     packageToLoad, realUserId));
535             return 1;
536         }
537         final AssetManager assets = res.getAssets();
538         try {
539             assets.setResourceResolutionLoggingEnabled(true);
540 
541             // first try as non-complex type ...
542             try {
543                 final TypedValue value = new TypedValue();
544                 res.getValue(fullyQualifiedResourceName, value, false /* resolveRefs */);
545                 final CharSequence valueString = value.coerceToString();
546                 final String resolution = assets.getLastResourceResolution();
547 
548                 res.getValue(fullyQualifiedResourceName, value, true /* resolveRefs */);
549                 final CharSequence resolvedString = value.coerceToString();
550 
551                 if (verbose) {
552                     out.println(resolution);
553                 }
554 
555                 if (valueString.equals(resolvedString)) {
556                     out.println(valueString);
557                 } else {
558                     out.println(valueString + " -> " + resolvedString);
559                 }
560                 return 0;
561             } catch (Resources.NotFoundException e) {
562                 // this is ok, resource could still be a complex type
563             }
564 
565             // ... then try as complex type
566             try {
567 
568                 final String pkg = matcher.group(1);
569                 final String type = matcher.group(2);
570                 final String name = matcher.group(3);
571                 final int resid = res.getIdentifier(name, type, pkg);
572                 if (resid == 0) {
573                     throw new Resources.NotFoundException();
574                 }
575                 final TypedArray array = res.obtainTypedArray(resid);
576                 if (verbose) {
577                     out.println(assets.getLastResourceResolution());
578                 }
579                 TypedValue tv = new TypedValue();
580                 for (int i = 0; i < array.length(); i++) {
581                     array.getValue(i, tv);
582                     out.println(tv.coerceToString());
583                 }
584                 array.recycle();
585                 return 0;
586             } catch (Resources.NotFoundException e) {
587                 // give up
588                 err.println("Error: failed to get the resource " + fullyQualifiedResourceName);
589                 return 1;
590             }
591         } finally {
592             assets.setResourceResolutionLoggingEnabled(false);
593         }
594     }
595 }
596