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.IOverlayManager;
23 import android.content.om.OverlayInfo;
24 import android.content.pm.PackageManager;
25 import android.content.res.AssetManager;
26 import android.content.res.Resources;
27 import android.content.res.TypedArray;
28 import android.os.RemoteException;
29 import android.os.ShellCommand;
30 import android.os.UserHandle;
31 import android.util.TypedValue;
32 
33 import java.io.PrintWriter;
34 import java.util.List;
35 import java.util.Map;
36 import java.util.regex.Matcher;
37 import java.util.regex.Pattern;
38 
39 /**
40  * Implementation of 'cmd overlay' commands.
41  *
42  * This class provides an interface to the OverlayManagerService via adb.
43  * Intended only for manual debugging. Execute 'adb exec-out cmd overlay help'
44  * for a list of available commands.
45  */
46 final class OverlayManagerShellCommand extends ShellCommand {
47     private final Context mContext;
48     private final IOverlayManager mInterface;
49 
OverlayManagerShellCommand(@onNull final Context ctx, @NonNull final IOverlayManager iom)50     OverlayManagerShellCommand(@NonNull final Context ctx, @NonNull final IOverlayManager iom) {
51         mContext = ctx;
52         mInterface = iom;
53     }
54 
55     @Override
onCommand(@ullable final String cmd)56     public int onCommand(@Nullable final String cmd) {
57         if (cmd == null) {
58             return handleDefaultCommands(cmd);
59         }
60         final PrintWriter err = getErrPrintWriter();
61         try {
62             switch (cmd) {
63                 case "list":
64                     return runList();
65                 case "enable":
66                     return runEnableDisable(true);
67                 case "disable":
68                     return runEnableDisable(false);
69                 case "enable-exclusive":
70                     return runEnableExclusive();
71                 case "set-priority":
72                     return runSetPriority();
73                 case "lookup":
74                     return runLookup();
75                 default:
76                     return handleDefaultCommands(cmd);
77             }
78         } catch (IllegalArgumentException e) {
79             err.println("Error: " + e.getMessage());
80         } catch (RemoteException e) {
81             err.println("Remote exception: " + e);
82         }
83         return -1;
84     }
85 
86     @Override
onHelp()87     public void onHelp() {
88         final PrintWriter out = getOutPrintWriter();
89         out.println("Overlay manager (overlay) commands:");
90         out.println("  help");
91         out.println("    Print this help text.");
92         out.println("  dump [--verbose] [--user USER_ID] [[FIELD] PACKAGE]");
93         out.println("    Print debugging information about the overlay manager.");
94         out.println("    With optional parameter PACKAGE, limit output to the specified");
95         out.println("    package. With optional parameter FIELD, limit output to");
96         out.println("    the value of that SettingsItem field. Field names are");
97         out.println("    case insensitive and out.println the m prefix can be omitted,");
98         out.println("    so the following are equivalent: mState, mstate, State, state.");
99         out.println("  list [--user USER_ID] [PACKAGE]");
100         out.println("    Print information about target and overlay packages.");
101         out.println("    Overlay packages are printed in priority order. With optional");
102         out.println("    parameter PACKAGE, limit output to the specified package.");
103         out.println("  enable [--user USER_ID] PACKAGE");
104         out.println("    Enable overlay package PACKAGE.");
105         out.println("  disable [--user USER_ID] PACKAGE");
106         out.println("    Disable overlay package PACKAGE.");
107         out.println("  enable-exclusive [--user USER_ID] [--category] PACKAGE");
108         out.println("    Enable overlay package PACKAGE and disable all other overlays for");
109         out.println("    its target package. If the --category option is given, only disables");
110         out.println("    other overlays in the same category.");
111         out.println("  set-priority [--user USER_ID] PACKAGE PARENT|lowest|highest");
112         out.println("    Change the priority of the overlay PACKAGE to be just higher than");
113         out.println("    the priority of PACKAGE_PARENT If PARENT is the special keyword");
114         out.println("    'lowest', change priority of PACKAGE to the lowest priority.");
115         out.println("    If PARENT is the special keyword 'highest', change priority of");
116         out.println("    PACKAGE to the highest priority.");
117         out.println("  lookup [--verbose] PACKAGE-TO-LOAD PACKAGE:TYPE/NAME");
118         out.println("    Load a package and print the value of a given resource");
119         out.println("    applying the current configuration and enabled overlays.");
120         out.println("    For a more fine-grained alernative, use 'idmap2 lookup'.");
121     }
122 
runList()123     private int runList() throws RemoteException {
124         final PrintWriter out = getOutPrintWriter();
125         final PrintWriter err = getErrPrintWriter();
126 
127         int userId = UserHandle.USER_SYSTEM;
128         String opt;
129         while ((opt = getNextOption()) != null) {
130             switch (opt) {
131                 case "--user":
132                     userId = UserHandle.parseUserArg(getNextArgRequired());
133                     break;
134                 default:
135                     err.println("Error: Unknown option: " + opt);
136                     return 1;
137             }
138         }
139 
140         final String packageName = getNextArg();
141         if (packageName != null) {
142             List<OverlayInfo> overlaysForTarget = mInterface.getOverlayInfosForTarget(
143                     packageName, userId);
144 
145             // If the package is not targeted by any overlays, check if the package is an overlay.
146             if (overlaysForTarget.isEmpty()) {
147                 final OverlayInfo info = mInterface.getOverlayInfo(packageName, userId);
148                 if (info != null) {
149                     printListOverlay(out, info);
150                 }
151                 return 0;
152             }
153 
154             out.println(packageName);
155 
156             // Print the overlays for the target.
157             final int n = overlaysForTarget.size();
158             for (int i = 0; i < n; i++) {
159                 printListOverlay(out, overlaysForTarget.get(i));
160             }
161 
162             return 0;
163         }
164 
165         // Print all overlays grouped by target package name.
166         final Map<String, List<OverlayInfo>> allOverlays = mInterface.getAllOverlays(userId);
167         for (final String targetPackageName : allOverlays.keySet()) {
168             out.println(targetPackageName);
169 
170             List<OverlayInfo> overlaysForTarget = allOverlays.get(targetPackageName);
171             final int n = overlaysForTarget.size();
172             for (int i = 0; i < n; i++) {
173                 printListOverlay(out, overlaysForTarget.get(i));
174             }
175             out.println();
176         }
177 
178         return 0;
179     }
180 
printListOverlay(PrintWriter out, OverlayInfo oi)181     private void printListOverlay(PrintWriter out, OverlayInfo oi) {
182         String status;
183         switch (oi.state) {
184             case OverlayInfo.STATE_ENABLED_IMMUTABLE:
185             case OverlayInfo.STATE_ENABLED:
186                 status = "[x]";
187                 break;
188             case OverlayInfo.STATE_DISABLED:
189                 status = "[ ]";
190                 break;
191             default:
192                 status = "---";
193                 break;
194         }
195         out.println(String.format("%s %s", status, oi.packageName));
196     }
197 
runEnableDisable(final boolean enable)198     private int runEnableDisable(final boolean enable) throws RemoteException {
199         final PrintWriter err = getErrPrintWriter();
200 
201         int userId = UserHandle.USER_SYSTEM;
202         String opt;
203         while ((opt = getNextOption()) != null) {
204             switch (opt) {
205                 case "--user":
206                     userId = UserHandle.parseUserArg(getNextArgRequired());
207                     break;
208                 default:
209                     err.println("Error: Unknown option: " + opt);
210                     return 1;
211             }
212         }
213 
214         final String packageName = getNextArgRequired();
215         return mInterface.setEnabled(packageName, enable, userId) ? 0 : 1;
216     }
217 
runEnableExclusive()218     private int runEnableExclusive() throws RemoteException {
219         final PrintWriter err = getErrPrintWriter();
220 
221         int userId = UserHandle.USER_SYSTEM;
222         boolean inCategory = false;
223         String opt;
224         while ((opt = getNextOption()) != null) {
225             switch (opt) {
226                 case "--user":
227                     userId = UserHandle.parseUserArg(getNextArgRequired());
228                     break;
229                 case "--category":
230                     inCategory = true;
231                     break;
232                 default:
233                     err.println("Error: Unknown option: " + opt);
234                     return 1;
235             }
236         }
237         final String overlay = getNextArgRequired();
238         if (inCategory) {
239             return mInterface.setEnabledExclusiveInCategory(overlay, userId) ? 0 : 1;
240         } else {
241             return mInterface.setEnabledExclusive(overlay, true, userId) ? 0 : 1;
242         }
243     }
244 
runSetPriority()245     private int runSetPriority() throws RemoteException {
246         final PrintWriter err = getErrPrintWriter();
247 
248         int userId = UserHandle.USER_SYSTEM;
249         String opt;
250         while ((opt = getNextOption()) != null) {
251             switch (opt) {
252                 case "--user":
253                     userId = UserHandle.parseUserArg(getNextArgRequired());
254                     break;
255                 default:
256                     err.println("Error: Unknown option: " + opt);
257                     return 1;
258             }
259         }
260 
261         final String packageName = getNextArgRequired();
262         final String newParentPackageName = getNextArgRequired();
263 
264         if ("highest".equals(newParentPackageName)) {
265             return mInterface.setHighestPriority(packageName, userId) ? 0 : 1;
266         } else if ("lowest".equals(newParentPackageName)) {
267             return mInterface.setLowestPriority(packageName, userId) ? 0 : 1;
268         } else {
269             return mInterface.setPriority(packageName, newParentPackageName, userId) ? 0 : 1;
270         }
271     }
272 
runLookup()273     private int runLookup() throws RemoteException {
274         final PrintWriter out = getOutPrintWriter();
275         final PrintWriter err = getErrPrintWriter();
276 
277         final boolean verbose = "--verbose".equals(getNextOption());
278 
279         final String packageToLoad = getNextArgRequired();
280 
281         final String fullyQualifiedResourceName = getNextArgRequired(); // package:type/name
282         final Pattern regex = Pattern.compile("(.*?):(.*?)/(.*?)");
283         final Matcher matcher = regex.matcher(fullyQualifiedResourceName);
284         if (!matcher.matches()) {
285             err.println("Error: bad resource name, doesn't match package:type/name");
286             return 1;
287         }
288 
289         final PackageManager pm = mContext.getPackageManager();
290         if (pm == null) {
291             err.println("Error: failed to get package manager");
292             return 1;
293         }
294 
295         final Resources res;
296         try {
297             res = pm.getResourcesForApplication(packageToLoad);
298         } catch (PackageManager.NameNotFoundException e) {
299             err.println("Error: failed to get resources for package " + packageToLoad);
300             return 1;
301         }
302         final AssetManager assets = res.getAssets();
303         try {
304             assets.setResourceResolutionLoggingEnabled(true);
305 
306             // first try as non-complex type ...
307             try {
308                 final TypedValue value = new TypedValue();
309                 res.getValue(fullyQualifiedResourceName, value, false /* resolveRefs */);
310                 final CharSequence valueString = value.coerceToString();
311                 final String resolution = assets.getLastResourceResolution();
312 
313                 res.getValue(fullyQualifiedResourceName, value, true /* resolveRefs */);
314                 final CharSequence resolvedString = value.coerceToString();
315 
316                 if (verbose) {
317                     out.println(resolution);
318                 }
319 
320                 if (valueString.equals(resolvedString)) {
321                     out.println(valueString);
322                 } else {
323                     out.println(valueString + " -> " + resolvedString);
324                 }
325                 return 0;
326             } catch (Resources.NotFoundException e) {
327                 // this is ok, resource could still be a complex type
328             }
329 
330             // ... then try as complex type
331             try {
332 
333                 final String pkg = matcher.group(1);
334                 final String type = matcher.group(2);
335                 final String name = matcher.group(3);
336                 final int resid = res.getIdentifier(name, type, pkg);
337                 if (resid == 0) {
338                     throw new Resources.NotFoundException();
339                 }
340                 final TypedArray array = res.obtainTypedArray(resid);
341                 if (verbose) {
342                     out.println(assets.getLastResourceResolution());
343                 }
344                 TypedValue tv = new TypedValue();
345                 for (int i = 0; i < array.length(); i++) {
346                     array.getValue(i, tv);
347                     out.println(tv.coerceToString());
348                 }
349                 array.recycle();
350                 return 0;
351             } catch (Resources.NotFoundException e) {
352                 // give up
353                 err.println("Error: failed to get the resource " + fullyQualifiedResourceName);
354                 return 1;
355             }
356         } finally {
357             assets.setResourceResolutionLoggingEnabled(false);
358         }
359     }
360 }
361