1/*
2 * Copyright (C) 2012 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.content.BroadcastReceiver;
20import android.content.Context;
21import android.content.Intent;
22import android.content.IntentFilter;
23import android.content.pm.PackageInfo;
24import android.content.pm.PackageManager;
25import android.os.Binder;
26import android.os.PatternMatcher;
27import android.os.Process;
28import android.os.ResultReceiver;
29import android.os.ShellCallback;
30import android.os.UserHandle;
31import android.util.Slog;
32import android.webkit.IWebViewUpdateService;
33import android.webkit.WebViewProviderInfo;
34import android.webkit.WebViewProviderResponse;
35
36import com.android.internal.util.DumpUtils;
37import com.android.server.SystemService;
38
39import java.io.FileDescriptor;
40import java.io.PrintWriter;
41import java.util.Arrays;
42
43/**
44 * Private service to wait for the updatable WebView to be ready for use.
45 * @hide
46 */
47public class WebViewUpdateService extends SystemService {
48
49    private static final String TAG = "WebViewUpdateService";
50
51    private BroadcastReceiver mWebViewUpdatedReceiver;
52    private WebViewUpdateServiceImpl mImpl;
53
54    static final int PACKAGE_CHANGED = 0;
55    static final int PACKAGE_ADDED = 1;
56    static final int PACKAGE_ADDED_REPLACED = 2;
57    static final int PACKAGE_REMOVED = 3;
58
59    public WebViewUpdateService(Context context) {
60        super(context);
61        mImpl = new WebViewUpdateServiceImpl(context, SystemImpl.getInstance());
62    }
63
64    @Override
65    public void onStart() {
66        mWebViewUpdatedReceiver = new BroadcastReceiver() {
67                @Override
68                public void onReceive(Context context, Intent intent) {
69                    int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, UserHandle.USER_NULL);
70                    switch (intent.getAction()) {
71                        case Intent.ACTION_PACKAGE_REMOVED:
72                            // When a package is replaced we will receive two intents, one
73                            // representing the removal of the old package and one representing the
74                            // addition of the new package.
75                            // In the case where we receive an intent to remove the old version of
76                            // the package that is being replaced we early-out here so that we don't
77                            // run the update-logic twice.
78                            if (intent.getExtras().getBoolean(Intent.EXTRA_REPLACING)) return;
79                            mImpl.packageStateChanged(packageNameFromIntent(intent),
80                                    PACKAGE_REMOVED, userId);
81                            break;
82                        case Intent.ACTION_PACKAGE_CHANGED:
83                            // Ensure that we only heed PACKAGE_CHANGED intents if they change an
84                            // entire package, not just a component
85                            if (entirePackageChanged(intent)) {
86                                mImpl.packageStateChanged(packageNameFromIntent(intent),
87                                        PACKAGE_CHANGED, userId);
88                            }
89                            break;
90                        case Intent.ACTION_PACKAGE_ADDED:
91                            mImpl.packageStateChanged(packageNameFromIntent(intent),
92                                    (intent.getExtras().getBoolean(Intent.EXTRA_REPLACING)
93                                     ? PACKAGE_ADDED_REPLACED : PACKAGE_ADDED), userId);
94                            break;
95                        case Intent.ACTION_USER_STARTED:
96                            mImpl.handleNewUser(userId);
97                            break;
98                        case Intent.ACTION_USER_REMOVED:
99                            mImpl.handleUserRemoved(userId);
100                            break;
101                    }
102                }
103        };
104        IntentFilter filter = new IntentFilter();
105        filter.addAction(Intent.ACTION_PACKAGE_ADDED);
106        filter.addAction(Intent.ACTION_PACKAGE_REMOVED);
107        filter.addAction(Intent.ACTION_PACKAGE_CHANGED);
108        filter.addDataScheme("package");
109        // Make sure we only receive intents for WebView packages from our config file.
110        for (WebViewProviderInfo provider : mImpl.getWebViewPackages()) {
111            filter.addDataSchemeSpecificPart(provider.packageName, PatternMatcher.PATTERN_LITERAL);
112        }
113
114        getContext().registerReceiverAsUser(mWebViewUpdatedReceiver, UserHandle.ALL, filter,
115                null /* broadcast permission */, null /* handler */);
116
117        IntentFilter userAddedFilter = new IntentFilter();
118        userAddedFilter.addAction(Intent.ACTION_USER_STARTED);
119        userAddedFilter.addAction(Intent.ACTION_USER_REMOVED);
120        getContext().registerReceiverAsUser(mWebViewUpdatedReceiver, UserHandle.ALL,
121                userAddedFilter, null /* broadcast permission */, null /* handler */);
122
123        publishBinderService("webviewupdate", new BinderService(), true /*allowIsolated*/);
124    }
125
126    public void prepareWebViewInSystemServer() {
127        mImpl.prepareWebViewInSystemServer();
128    }
129
130    private static String packageNameFromIntent(Intent intent) {
131        return intent.getDataString().substring("package:".length());
132    }
133
134    /**
135     * Returns whether the entire package from an ACTION_PACKAGE_CHANGED intent was changed (rather
136     * than just one of its components).
137     * @hide
138     */
139    public static boolean entirePackageChanged(Intent intent) {
140        String[] componentList =
141            intent.getStringArrayExtra(Intent.EXTRA_CHANGED_COMPONENT_NAME_LIST);
142        return Arrays.asList(componentList).contains(
143                intent.getDataString().substring("package:".length()));
144    }
145
146    private class BinderService extends IWebViewUpdateService.Stub {
147
148        @Override
149        public void onShellCommand(FileDescriptor in, FileDescriptor out,
150                FileDescriptor err, String[] args, ShellCallback callback,
151                ResultReceiver resultReceiver) {
152            (new WebViewUpdateServiceShellCommand(this)).exec(
153                    this, in, out, err, args, callback, resultReceiver);
154        }
155
156
157        /**
158         * The shared relro process calls this to notify us that it's done trying to create a relro
159         * file. This method gets called even if the relro creation has failed or the process
160         * crashed.
161         */
162        @Override // Binder call
163        public void notifyRelroCreationCompleted() {
164            // Verify that the caller is either the shared relro process (nominal case) or the
165            // system server (only in the case the relro process crashes and we get here via the
166            // crashHandler).
167            if (Binder.getCallingUid() != Process.SHARED_RELRO_UID &&
168                    Binder.getCallingUid() != Process.SYSTEM_UID) {
169                return;
170            }
171
172            long callingId = Binder.clearCallingIdentity();
173            try {
174                WebViewUpdateService.this.mImpl.notifyRelroCreationCompleted();
175            } finally {
176                Binder.restoreCallingIdentity(callingId);
177            }
178        }
179
180        /**
181         * WebViewFactory calls this to block WebView loading until the relro file is created.
182         * Returns the WebView provider for which we create relro files.
183         */
184        @Override // Binder call
185        public WebViewProviderResponse waitForAndGetProvider() {
186            // The WebViewUpdateService depends on the prepareWebViewInSystemServer call, which
187            // happens later (during the PHASE_ACTIVITY_MANAGER_READY) in SystemServer.java. If
188            // another service there tries to bring up a WebView in the between, the wait below
189            // would deadlock without the check below.
190            if (Binder.getCallingPid() == Process.myPid()) {
191                throw new IllegalStateException("Cannot create a WebView from the SystemServer");
192            }
193
194            return WebViewUpdateService.this.mImpl.waitForAndGetProvider();
195        }
196
197        /**
198         * This is called from DeveloperSettings when the user changes WebView provider.
199         */
200        @Override // Binder call
201        public String changeProviderAndSetting(String newProvider) {
202            if (getContext().checkCallingPermission(
203                        android.Manifest.permission.WRITE_SECURE_SETTINGS)
204                    != PackageManager.PERMISSION_GRANTED) {
205                String msg = "Permission Denial: changeProviderAndSetting() from pid="
206                        + Binder.getCallingPid()
207                        + ", uid=" + Binder.getCallingUid()
208                        + " requires " + android.Manifest.permission.WRITE_SECURE_SETTINGS;
209                Slog.w(TAG, msg);
210                throw new SecurityException(msg);
211            }
212
213            long callingId = Binder.clearCallingIdentity();
214            try {
215                return WebViewUpdateService.this.mImpl.changeProviderAndSetting(
216                        newProvider);
217            } finally {
218                Binder.restoreCallingIdentity(callingId);
219            }
220        }
221
222        @Override // Binder call
223        public WebViewProviderInfo[] getValidWebViewPackages() {
224            return WebViewUpdateService.this.mImpl.getValidWebViewPackages();
225        }
226
227        @Override // Binder call
228        public WebViewProviderInfo[] getAllWebViewPackages() {
229            return WebViewUpdateService.this.mImpl.getWebViewPackages();
230        }
231
232        @Override // Binder call
233        public String getCurrentWebViewPackageName() {
234            PackageInfo pi = WebViewUpdateService.this.mImpl.getCurrentWebViewPackage();
235            return pi == null ? null : pi.packageName;
236        }
237
238        @Override // Binder call
239        public PackageInfo getCurrentWebViewPackage() {
240            return WebViewUpdateService.this.mImpl.getCurrentWebViewPackage();
241        }
242
243        @Override // Binder call
244        public boolean isFallbackPackage(String packageName) {
245            return WebViewUpdateService.this.mImpl.isFallbackPackage(packageName);
246        }
247
248        @Override // Binder call
249        public void enableFallbackLogic(boolean enable) {
250            if (getContext().checkCallingPermission(
251                        android.Manifest.permission.WRITE_SECURE_SETTINGS)
252                    != PackageManager.PERMISSION_GRANTED) {
253                String msg = "Permission Denial: enableFallbackLogic() from pid="
254                        + Binder.getCallingPid()
255                        + ", uid=" + Binder.getCallingUid()
256                        + " requires " + android.Manifest.permission.WRITE_SECURE_SETTINGS;
257                Slog.w(TAG, msg);
258                throw new SecurityException(msg);
259            }
260
261            long callingId = Binder.clearCallingIdentity();
262            try {
263                WebViewUpdateService.this.mImpl.enableFallbackLogic(enable);
264            } finally {
265                Binder.restoreCallingIdentity(callingId);
266            }
267        }
268
269        @Override // Binder call
270        public boolean isMultiProcessEnabled() {
271            return WebViewUpdateService.this.mImpl.isMultiProcessEnabled();
272        }
273
274        @Override // Binder call
275        public void enableMultiProcess(boolean enable) {
276            if (getContext().checkCallingPermission(
277                        android.Manifest.permission.WRITE_SECURE_SETTINGS)
278                    != PackageManager.PERMISSION_GRANTED) {
279                String msg = "Permission Denial: enableMultiProcess() from pid="
280                        + Binder.getCallingPid()
281                        + ", uid=" + Binder.getCallingUid()
282                        + " requires " + android.Manifest.permission.WRITE_SECURE_SETTINGS;
283                Slog.w(TAG, msg);
284                throw new SecurityException(msg);
285            }
286
287            long callingId = Binder.clearCallingIdentity();
288            try {
289                WebViewUpdateService.this.mImpl.enableMultiProcess(enable);
290            } finally {
291                Binder.restoreCallingIdentity(callingId);
292            }
293        }
294
295        @Override
296        protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
297            if (!DumpUtils.checkDumpPermission(getContext(), TAG, pw)) return;
298            WebViewUpdateService.this.mImpl.dumpState(pw);
299        }
300    }
301}
302