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