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
17package com.android.server.webkit;
18
19import android.app.ActivityManager;
20import android.app.AppGlobals;
21import android.content.Context;
22import android.content.pm.ApplicationInfo;
23import android.content.pm.IPackageDeleteObserver;
24import android.content.pm.PackageInfo;
25import android.content.pm.PackageManager;
26import android.content.pm.PackageManager.NameNotFoundException;
27import android.content.pm.UserInfo;
28import android.content.res.XmlResourceParser;
29import android.database.ContentObserver;
30import android.os.Build;
31import android.os.RemoteException;
32import android.os.UserHandle;
33import android.os.UserManager;
34import android.provider.Settings.Global;
35import android.provider.Settings;
36import android.util.AndroidRuntimeException;
37import android.util.Log;
38import android.webkit.UserPackage;
39import android.webkit.WebViewFactory;
40import android.webkit.WebViewProviderInfo;
41import android.webkit.WebViewZygote;
42
43import com.android.internal.util.XmlUtils;
44
45import java.io.IOException;
46import java.util.ArrayList;
47import java.util.List;
48
49import org.xmlpull.v1.XmlPullParserException;
50
51/**
52 * Default implementation for the WebView preparation Utility interface.
53 * @hide
54 */
55public class SystemImpl implements SystemInterface {
56    private static final String TAG = SystemImpl.class.getSimpleName();
57    private static final String TAG_START = "webviewproviders";
58    private static final String TAG_WEBVIEW_PROVIDER = "webviewprovider";
59    private static final String TAG_PACKAGE_NAME = "packageName";
60    private static final String TAG_DESCRIPTION = "description";
61    // Whether or not the provider must be explicitly chosen by the user to be used.
62    private static final String TAG_AVAILABILITY = "availableByDefault";
63    private static final String TAG_SIGNATURE = "signature";
64    private static final String TAG_FALLBACK = "isFallback";
65    private final WebViewProviderInfo[] mWebViewProviderPackages;
66
67    // Initialization-on-demand holder idiom for getting the WebView provider packages once and
68    // for all in a thread-safe manner.
69    private static class LazyHolder {
70        private static final SystemImpl INSTANCE = new SystemImpl();
71    }
72
73    public static SystemImpl getInstance() {
74        return LazyHolder.INSTANCE;
75    }
76
77    private SystemImpl() {
78        int numFallbackPackages = 0;
79        int numAvailableByDefaultPackages = 0;
80        int numAvByDefaultAndNotFallback = 0;
81        XmlResourceParser parser = null;
82        List<WebViewProviderInfo> webViewProviders = new ArrayList<WebViewProviderInfo>();
83        try {
84            parser = AppGlobals.getInitialApplication().getResources().getXml(
85                    com.android.internal.R.xml.config_webview_packages);
86            XmlUtils.beginDocument(parser, TAG_START);
87            while(true) {
88                XmlUtils.nextElement(parser);
89                String element = parser.getName();
90                if (element == null) {
91                    break;
92                }
93                if (element.equals(TAG_WEBVIEW_PROVIDER)) {
94                    String packageName = parser.getAttributeValue(null, TAG_PACKAGE_NAME);
95                    if (packageName == null) {
96                        throw new AndroidRuntimeException(
97                                "WebView provider in framework resources missing package name");
98                    }
99                    String description = parser.getAttributeValue(null, TAG_DESCRIPTION);
100                    if (description == null) {
101                        throw new AndroidRuntimeException(
102                                "WebView provider in framework resources missing description");
103                    }
104                    boolean availableByDefault = "true".equals(
105                            parser.getAttributeValue(null, TAG_AVAILABILITY));
106                    boolean isFallback = "true".equals(
107                            parser.getAttributeValue(null, TAG_FALLBACK));
108                    WebViewProviderInfo currentProvider = new WebViewProviderInfo(
109                            packageName, description, availableByDefault, isFallback,
110                            readSignatures(parser));
111                    if (currentProvider.isFallback) {
112                        numFallbackPackages++;
113                        if (!currentProvider.availableByDefault) {
114                            throw new AndroidRuntimeException(
115                                    "Each WebView fallback package must be available by default.");
116                        }
117                        if (numFallbackPackages > 1) {
118                            throw new AndroidRuntimeException(
119                                    "There can be at most one WebView fallback package.");
120                        }
121                    }
122                    if (currentProvider.availableByDefault) {
123                        numAvailableByDefaultPackages++;
124                        if (!currentProvider.isFallback) {
125                            numAvByDefaultAndNotFallback++;
126                        }
127                    }
128                    webViewProviders.add(currentProvider);
129                }
130                else {
131                    Log.e(TAG, "Found an element that is not a WebView provider");
132                }
133            }
134        } catch (XmlPullParserException | IOException e) {
135            throw new AndroidRuntimeException("Error when parsing WebView config " + e);
136        } finally {
137            if (parser != null) parser.close();
138        }
139        if (numAvailableByDefaultPackages == 0) {
140            throw new AndroidRuntimeException("There must be at least one WebView package "
141                    + "that is available by default");
142        }
143        if (numAvByDefaultAndNotFallback == 0) {
144            throw new AndroidRuntimeException("There must be at least one WebView package "
145                    + "that is available by default and not a fallback");
146        }
147        mWebViewProviderPackages =
148                webViewProviders.toArray(new WebViewProviderInfo[webViewProviders.size()]);
149    }
150    /**
151     * Returns all packages declared in the framework resources as potential WebView providers.
152     * @hide
153     * */
154    @Override
155    public WebViewProviderInfo[] getWebViewPackages() {
156        return mWebViewProviderPackages;
157    }
158
159    public int getFactoryPackageVersion(String packageName) throws NameNotFoundException {
160        PackageManager pm = AppGlobals.getInitialApplication().getPackageManager();
161        return pm.getPackageInfo(packageName, PackageManager.MATCH_FACTORY_ONLY).versionCode;
162    }
163
164    /**
165     * Reads all signatures at the current depth (within the current provider) from the XML parser.
166     */
167    private static String[] readSignatures(XmlResourceParser parser) throws IOException,
168            XmlPullParserException {
169        List<String> signatures = new ArrayList<String>();
170        int outerDepth = parser.getDepth();
171        while(XmlUtils.nextElementWithin(parser, outerDepth)) {
172            if (parser.getName().equals(TAG_SIGNATURE)) {
173                // Parse the value within the signature tag
174                String signature = parser.nextText();
175                signatures.add(signature);
176            } else {
177                Log.e(TAG, "Found an element in a webview provider that is not a signature");
178            }
179        }
180        return signatures.toArray(new String[signatures.size()]);
181    }
182
183    @Override
184    public int onWebViewProviderChanged(PackageInfo packageInfo) {
185        return WebViewFactory.onWebViewProviderChanged(packageInfo);
186    }
187
188    @Override
189    public String getUserChosenWebViewProvider(Context context) {
190        return Settings.Global.getString(context.getContentResolver(),
191                Settings.Global.WEBVIEW_PROVIDER);
192    }
193
194    @Override
195    public void updateUserSetting(Context context, String newProviderName) {
196        Settings.Global.putString(context.getContentResolver(),
197                Settings.Global.WEBVIEW_PROVIDER,
198                newProviderName == null ? "" : newProviderName);
199    }
200
201    @Override
202    public void killPackageDependents(String packageName) {
203        try {
204            ActivityManager.getService().killPackageDependents(packageName,
205                    UserHandle.USER_ALL);
206        } catch (RemoteException e) {
207        }
208    }
209
210    @Override
211    public boolean isFallbackLogicEnabled() {
212        // Note that this is enabled by default (i.e. if the setting hasn't been set).
213        return Settings.Global.getInt(AppGlobals.getInitialApplication().getContentResolver(),
214                Settings.Global.WEBVIEW_FALLBACK_LOGIC_ENABLED, 1) == 1;
215    }
216
217    @Override
218    public void enableFallbackLogic(boolean enable) {
219        Settings.Global.putInt(AppGlobals.getInitialApplication().getContentResolver(),
220                Settings.Global.WEBVIEW_FALLBACK_LOGIC_ENABLED, enable ? 1 : 0);
221    }
222
223    @Override
224    public void uninstallAndDisablePackageForAllUsers(Context context, String packageName) {
225        enablePackageForAllUsers(context, packageName, false);
226        try {
227            PackageManager pm = AppGlobals.getInitialApplication().getPackageManager();
228            ApplicationInfo applicationInfo = pm.getApplicationInfo(packageName, 0);
229            if (applicationInfo != null && applicationInfo.isUpdatedSystemApp()) {
230                pm.deletePackage(packageName, new IPackageDeleteObserver.Stub() {
231                        public void packageDeleted(String packageName, int returnCode) {
232                            enablePackageForAllUsers(context, packageName, false);
233                        }
234                    }, PackageManager.DELETE_SYSTEM_APP | PackageManager.DELETE_ALL_USERS);
235            }
236        } catch (NameNotFoundException e) {
237        }
238    }
239
240    @Override
241    public void enablePackageForAllUsers(Context context, String packageName, boolean enable) {
242        UserManager userManager = (UserManager)context.getSystemService(Context.USER_SERVICE);
243        for(UserInfo userInfo : userManager.getUsers()) {
244            enablePackageForUser(packageName, enable, userInfo.id);
245        }
246    }
247
248    @Override
249    public void enablePackageForUser(String packageName, boolean enable, int userId) {
250        try {
251            AppGlobals.getPackageManager().setApplicationEnabledSetting(
252                    packageName,
253                    enable ? PackageManager.COMPONENT_ENABLED_STATE_DEFAULT :
254                    PackageManager.COMPONENT_ENABLED_STATE_DISABLED_USER, 0,
255                    userId, null);
256        } catch (RemoteException | IllegalArgumentException e) {
257            Log.w(TAG, "Tried to " + (enable ? "enable " : "disable ") + packageName
258                    + " for user " + userId + ": " + e);
259        }
260    }
261
262    @Override
263    public boolean systemIsDebuggable() {
264        return Build.IS_DEBUGGABLE;
265    }
266
267    @Override
268    public PackageInfo getPackageInfoForProvider(WebViewProviderInfo configInfo)
269            throws NameNotFoundException {
270        PackageManager pm = AppGlobals.getInitialApplication().getPackageManager();
271        return pm.getPackageInfo(configInfo.packageName, PACKAGE_FLAGS);
272    }
273
274    @Override
275    public List<UserPackage> getPackageInfoForProviderAllUsers(Context context,
276            WebViewProviderInfo configInfo) {
277        return UserPackage.getPackageInfosAllUsers(context, configInfo.packageName, PACKAGE_FLAGS);
278    }
279
280    @Override
281    public int getMultiProcessSetting(Context context) {
282        return Settings.Global.getInt(context.getContentResolver(),
283                                      Settings.Global.WEBVIEW_MULTIPROCESS, 0);
284    }
285
286    @Override
287    public void setMultiProcessSetting(Context context, int value) {
288        Settings.Global.putInt(context.getContentResolver(),
289                               Settings.Global.WEBVIEW_MULTIPROCESS, value);
290    }
291
292    @Override
293    public void notifyZygote(boolean enableMultiProcess) {
294        WebViewZygote.setMultiprocessEnabled(enableMultiProcess);
295    }
296
297    @Override
298    public boolean isMultiProcessDefaultEnabled() {
299        return true;
300    }
301
302    // flags declaring we want extra info from the package manager for webview providers
303    private final static int PACKAGE_FLAGS = PackageManager.GET_META_DATA
304            | PackageManager.GET_SIGNATURES | PackageManager.MATCH_DEBUG_TRIAGED_MISSING
305            | PackageManager.MATCH_ANY_USER;
306}
307