blob: 06565c74432c9ea412a88bc630de0a6cf8acc2cf [file] [log] [blame]
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001/*
2 * Copyright (C) 2008 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;
18
19import static android.os.FileObserver.*;
20import static android.os.ParcelFileDescriptor.*;
Christopher Tate111bd4a2009-06-24 17:29:38 -070021
Dianne Hackborn8cc6a502009-08-05 21:29:42 -070022import android.app.IWallpaperManager;
23import android.app.IWallpaperManagerCallback;
Christopher Tate111bd4a2009-06-24 17:29:38 -070024import android.backup.BackupManager;
Dianne Hackborn4c62fc02009-08-08 20:40:27 -070025import android.content.ComponentName;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080026import android.content.Context;
27import android.content.Intent;
Dianne Hackborn4c62fc02009-08-08 20:40:27 -070028import android.content.ServiceConnection;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080029import android.content.pm.PackageManager;
Dianne Hackborn4c62fc02009-08-08 20:40:27 -070030import android.content.pm.ResolveInfo;
31import android.content.pm.ServiceInfo;
Joe Onorato9bb8fd72009-07-28 18:24:51 -070032import android.content.pm.PackageManager.NameNotFoundException;
33import android.content.res.Resources;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080034import android.os.Binder;
Dianne Hackborn4c62fc02009-08-08 20:40:27 -070035import android.os.IBinder;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080036import android.os.RemoteException;
37import android.os.FileObserver;
38import android.os.ParcelFileDescriptor;
39import android.os.RemoteCallbackList;
Dianne Hackborn4c62fc02009-08-08 20:40:27 -070040import android.os.ServiceManager;
41import android.service.wallpaper.IWallpaperConnection;
42import android.service.wallpaper.IWallpaperEngine;
43import android.service.wallpaper.IWallpaperService;
44import android.service.wallpaper.WallpaperService;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080045import android.util.Log;
Joe Onorato9bb8fd72009-07-28 18:24:51 -070046import android.util.Xml;
Dianne Hackborn4c62fc02009-08-08 20:40:27 -070047import android.view.IWindowManager;
48import android.view.WindowManager;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080049
Joe Onorato9bb8fd72009-07-28 18:24:51 -070050import java.io.IOException;
51import java.io.InputStream;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080052import java.io.File;
53import java.io.FileNotFoundException;
Joe Onorato9bb8fd72009-07-28 18:24:51 -070054import java.io.FileInputStream;
55import java.io.FileOutputStream;
Dianne Hackborn4c62fc02009-08-08 20:40:27 -070056import java.util.List;
Joe Onorato9bb8fd72009-07-28 18:24:51 -070057
58import org.xmlpull.v1.XmlPullParser;
59import org.xmlpull.v1.XmlPullParserException;
60import org.xmlpull.v1.XmlSerializer;
61
62import com.android.internal.util.FastXmlSerializer;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080063
Dianne Hackborn8cc6a502009-08-05 21:29:42 -070064class WallpaperManagerService extends IWallpaperManager.Stub {
Dianne Hackborn4c62fc02009-08-08 20:40:27 -070065 static final String TAG = "WallpaperService";
66 static final boolean DEBUG = true;
Joe Onorato9bb8fd72009-07-28 18:24:51 -070067
Dianne Hackborn4c62fc02009-08-08 20:40:27 -070068 Object mLock = new Object();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080069
70 private static final File WALLPAPER_DIR = new File(
71 "/data/data/com.android.settings/files");
72 private static final String WALLPAPER = "wallpaper";
73 private static final File WALLPAPER_FILE = new File(WALLPAPER_DIR, WALLPAPER);
74
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080075 /**
76 * List of callbacks registered they should each be notified
77 * when the wallpaper is changed.
78 */
Dianne Hackborn8cc6a502009-08-05 21:29:42 -070079 private final RemoteCallbackList<IWallpaperManagerCallback> mCallbacks
80 = new RemoteCallbackList<IWallpaperManagerCallback>();
Joe Onorato9bb8fd72009-07-28 18:24:51 -070081
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080082 /**
83 * Observes the wallpaper for changes and notifies all IWallpaperServiceCallbacks
84 * that the wallpaper has changed. The CREATE is triggered when there is no
85 * wallpaper set and is created for the first time. The CLOSE_WRITE is triggered
86 * everytime the wallpaper is changed.
87 */
88 private final FileObserver mWallpaperObserver = new FileObserver(
Joe Onorato9bb8fd72009-07-28 18:24:51 -070089 WALLPAPER_DIR.getAbsolutePath(), CREATE | CLOSE_WRITE | DELETE | DELETE_SELF) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080090 @Override
91 public void onEvent(int event, String path) {
Joe Onoratoe712ee32009-07-29 16:23:58 -070092 if (path == null) {
93 return;
94 }
Joe Onorato9bb8fd72009-07-28 18:24:51 -070095 synchronized (mLock) {
Joe Onorato9bb8fd72009-07-28 18:24:51 -070096 // changing the wallpaper means we'll need to back up the new one
97 long origId = Binder.clearCallingIdentity();
98 BackupManager bm = new BackupManager(mContext);
99 bm.dataChanged();
100 Binder.restoreCallingIdentity(origId);
101
102 File changedFile = new File(WALLPAPER_DIR, path);
103 if (WALLPAPER_FILE.equals(changedFile)) {
104 notifyCallbacksLocked();
105 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800106 }
107 }
108 };
109
Dianne Hackborn4c62fc02009-08-08 20:40:27 -0700110 final Context mContext;
111 final IWindowManager mIWindowManager;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800112
Dianne Hackborn4c62fc02009-08-08 20:40:27 -0700113 int mWidth = -1;
114 int mHeight = -1;
115 String mName = "";
116 ComponentName mWallpaperComponent;
117 WallpaperConnection mWallpaperConnection;
118
119 class WallpaperConnection extends IWallpaperConnection.Stub
120 implements ServiceConnection {
121 final Binder mToken = new Binder();
122 IWallpaperService mService;
123 IWallpaperEngine mEngine;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800124
Dianne Hackborn4c62fc02009-08-08 20:40:27 -0700125 public void onServiceConnected(ComponentName name, IBinder service) {
126 synchronized (mLock) {
127 if (mWallpaperConnection == this) {
128 mService = IWallpaperService.Stub.asInterface(service);
129 attachServiceLocked(this);
130 }
131 }
132 }
133
134 public void onServiceDisconnected(ComponentName name) {
135 synchronized (mLock) {
136 mService = null;
137 mEngine = null;
138 }
139 }
140
141 public void attachEngine(IWallpaperEngine engine) {
142 mEngine = engine;
143 }
144
145 public ParcelFileDescriptor setWallpaper(String name) {
146 synchronized (mLock) {
147 if (mWallpaperConnection == this) {
148 ParcelFileDescriptor pfd = updateWallpaperBitmapLocked(name);
149 if (pfd != null) {
150 saveSettingsLocked();
151 }
152 return pfd;
153 }
154 return null;
155 }
156 }
157 }
158
Dianne Hackborn8cc6a502009-08-05 21:29:42 -0700159 public WallpaperManagerService(Context context) {
Dianne Hackborn4c62fc02009-08-08 20:40:27 -0700160 if (DEBUG) Log.d(TAG, "WallpaperService startup");
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800161 mContext = context;
Dianne Hackborn4c62fc02009-08-08 20:40:27 -0700162 mIWindowManager = IWindowManager.Stub.asInterface(
163 ServiceManager.getService(Context.WINDOW_SERVICE));
Joe Onoratoe712ee32009-07-29 16:23:58 -0700164 WALLPAPER_DIR.mkdirs();
Joe Onorato9bb8fd72009-07-28 18:24:51 -0700165 loadSettingsLocked();
Joe Onoratoe712ee32009-07-29 16:23:58 -0700166 mWallpaperObserver.startWatching();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800167 }
168
169 @Override
170 protected void finalize() throws Throwable {
171 super.finalize();
172 mWallpaperObserver.stopWatching();
173 }
174
175 public void clearWallpaper() {
Joe Onorato9bb8fd72009-07-28 18:24:51 -0700176 synchronized (mLock) {
177 File f = WALLPAPER_FILE;
178 if (f.exists()) {
179 f.delete();
180 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800181 }
182 }
183
184 public void setDimensionHints(int width, int height) throws RemoteException {
185 checkPermission(android.Manifest.permission.SET_WALLPAPER_HINTS);
186
187 if (width <= 0 || height <= 0) {
188 throw new IllegalArgumentException("width and height must be > 0");
189 }
190
Joe Onorato9bb8fd72009-07-28 18:24:51 -0700191 synchronized (mLock) {
192 if (width != mWidth || height != mHeight) {
193 mWidth = width;
194 mHeight = height;
195 saveSettingsLocked();
196 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800197 }
198 }
199
200 public int getWidthHint() throws RemoteException {
Joe Onorato9bb8fd72009-07-28 18:24:51 -0700201 synchronized (mLock) {
202 return mWidth;
203 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800204 }
205
206 public int getHeightHint() throws RemoteException {
Joe Onorato9bb8fd72009-07-28 18:24:51 -0700207 synchronized (mLock) {
208 return mHeight;
209 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800210 }
211
Dianne Hackborn8cc6a502009-08-05 21:29:42 -0700212 public ParcelFileDescriptor getWallpaper(IWallpaperManagerCallback cb) {
Joe Onorato9bb8fd72009-07-28 18:24:51 -0700213 synchronized (mLock) {
214 try {
215 mCallbacks.register(cb);
216 File f = WALLPAPER_FILE;
217 if (!f.exists()) {
218 return null;
219 }
220 return ParcelFileDescriptor.open(f, MODE_READ_ONLY);
221 } catch (FileNotFoundException e) {
222 /* Shouldn't happen as we check to see if the file exists */
Dianne Hackborn4c62fc02009-08-08 20:40:27 -0700223 Log.w(TAG, "Error getting wallpaper", e);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800224 }
Joe Onorato9bb8fd72009-07-28 18:24:51 -0700225 return null;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800226 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800227 }
228
Joe Onorato9bb8fd72009-07-28 18:24:51 -0700229 public ParcelFileDescriptor setWallpaper(String name) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800230 checkPermission(android.Manifest.permission.SET_WALLPAPER);
Joe Onorato9bb8fd72009-07-28 18:24:51 -0700231 synchronized (mLock) {
Dianne Hackborn4c62fc02009-08-08 20:40:27 -0700232 ParcelFileDescriptor pfd = updateWallpaperBitmapLocked(name);
233 if (pfd != null) {
234 clearWallpaperComponentLocked();
235 saveSettingsLocked();
Joe Onorato9bb8fd72009-07-28 18:24:51 -0700236 }
Dianne Hackborn4c62fc02009-08-08 20:40:27 -0700237 return pfd;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800238 }
239 }
240
Dianne Hackborn4c62fc02009-08-08 20:40:27 -0700241 ParcelFileDescriptor updateWallpaperBitmapLocked(String name) {
242 if (name == null) name = "";
243 try {
244 ParcelFileDescriptor fd = ParcelFileDescriptor.open(WALLPAPER_FILE,
245 MODE_CREATE|MODE_READ_WRITE);
246 mName = name;
247 return fd;
248 } catch (FileNotFoundException e) {
249 Log.w(TAG, "Error setting wallpaper", e);
250 }
251 return null;
252 }
253
254 public void setWallpaperComponent(ComponentName name) {
255 checkPermission(android.Manifest.permission.SET_WALLPAPER_COMPONENT);
256 synchronized (mLock) {
257 final long ident = Binder.clearCallingIdentity();
258 try {
259 ServiceInfo si = mContext.getPackageManager().getServiceInfo(name,
260 PackageManager.GET_META_DATA | PackageManager.GET_PERMISSIONS);
261 if (!android.Manifest.permission.BIND_WALLPAPER.equals(si.permission)) {
262 throw new SecurityException("Selected service does not require "
263 + android.Manifest.permission.BIND_WALLPAPER
264 + ": " + name);
265 }
266
267 // Make sure the selected service is actually a wallpaper service.
268 Intent intent = new Intent(WallpaperService.SERVICE_INTERFACE);
269 List<ResolveInfo> ris = mContext.getPackageManager()
270 .queryIntentServices(intent, 0);
271 for (int i=0; i<ris.size(); i++) {
272 ServiceInfo rsi = ris.get(i).serviceInfo;
273 if (rsi.name.equals(si.name) &&
274 rsi.packageName.equals(si.packageName)) {
275 ris = null;
276 break;
277 }
278 }
279 if (ris != null) {
280 throw new SecurityException("Selected service is not a wallpaper: "
281 + name);
282 }
283
284 // Bind the service!
285 WallpaperConnection newConn = new WallpaperConnection();
286 intent.setComponent(name);
287 if (!mContext.bindService(intent, newConn,
288 Context.BIND_AUTO_CREATE)) {
289 throw new IllegalArgumentException("Unable to bind service: "
290 + name);
291 }
292
293 clearWallpaperComponentLocked();
294 mWallpaperComponent = null;
295 mWallpaperConnection = newConn;
296 try {
297 if (DEBUG) Log.v(TAG, "Adding window token: " + newConn.mToken);
298 mIWindowManager.addWindowToken(newConn.mToken,
299 WindowManager.LayoutParams.TYPE_WALLPAPER);
300 } catch (RemoteException e) {
301 }
302
303 } catch (PackageManager.NameNotFoundException e) {
304 throw new IllegalArgumentException("Unknown component " + name);
305 } finally {
306 Binder.restoreCallingIdentity(ident);
307 }
308 }
309 }
310
311 void clearWallpaperComponentLocked() {
312 mWallpaperComponent = null;
313 if (mWallpaperConnection != null) {
314 if (mWallpaperConnection.mEngine != null) {
315 try {
316 mWallpaperConnection.mEngine.destroy();
317 } catch (RemoteException e) {
318 }
319 }
320 mContext.unbindService(mWallpaperConnection);
321 mWallpaperConnection = null;
322 }
323 }
324
325 void attachServiceLocked(WallpaperConnection conn) {
326 try {
327 conn.mService.attach(conn, conn.mToken, mWidth, mHeight);
328 } catch (RemoteException e) {
329 Log.w(TAG, "Failed attaching wallpaper; clearing", e);
330 clearWallpaperComponentLocked();
331 }
332 }
333
Joe Onorato9bb8fd72009-07-28 18:24:51 -0700334 private void notifyCallbacksLocked() {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800335 final int n = mCallbacks.beginBroadcast();
336 for (int i = 0; i < n; i++) {
337 try {
338 mCallbacks.getBroadcastItem(i).onWallpaperChanged();
339 } catch (RemoteException e) {
340
341 // The RemoteCallbackList will take care of removing
342 // the dead object for us.
343 }
344 }
345 mCallbacks.finishBroadcast();
346 final Intent intent = new Intent(Intent.ACTION_WALLPAPER_CHANGED);
347 mContext.sendBroadcast(intent);
348 }
349
350 private void checkPermission(String permission) {
Joe Onorato9bb8fd72009-07-28 18:24:51 -0700351 if (PackageManager.PERMISSION_GRANTED!= mContext.checkCallingOrSelfPermission(permission)) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800352 throw new SecurityException("Access denied to process: " + Binder.getCallingPid()
353 + ", must have permission " + permission);
354 }
355 }
Joe Onorato9bb8fd72009-07-28 18:24:51 -0700356
357 private static JournaledFile makeJournaledFile() {
358 final String base = "/data/system/wallpaper_info.xml";
359 return new JournaledFile(new File(base), new File(base + ".tmp"));
360 }
361
362 private void saveSettingsLocked() {
363 JournaledFile journal = makeJournaledFile();
364 FileOutputStream stream = null;
365 try {
366 stream = new FileOutputStream(journal.chooseForWrite(), false);
367 XmlSerializer out = new FastXmlSerializer();
368 out.setOutput(stream, "utf-8");
369 out.startDocument(null, true);
370
371 out.startTag(null, "wp");
372 out.attribute(null, "width", Integer.toString(mWidth));
373 out.attribute(null, "height", Integer.toString(mHeight));
374 out.attribute(null, "name", mName);
Dianne Hackborn4c62fc02009-08-08 20:40:27 -0700375 if (mWallpaperComponent != null) {
376 out.attribute(null, "component",
377 mWallpaperComponent.flattenToShortString());
378 }
Joe Onorato9bb8fd72009-07-28 18:24:51 -0700379 out.endTag(null, "wp");
380
381 out.endDocument();
382 stream.close();
383 journal.commit();
384 } catch (IOException e) {
385 try {
386 if (stream != null) {
387 stream.close();
388 }
389 } catch (IOException ex) {
390 // Ignore
391 }
392 journal.rollback();
393 }
394 }
395
396 private void loadSettingsLocked() {
397 JournaledFile journal = makeJournaledFile();
398 FileInputStream stream = null;
399 File file = journal.chooseForRead();
400 boolean success = false;
401 try {
402 stream = new FileInputStream(file);
403 XmlPullParser parser = Xml.newPullParser();
404 parser.setInput(stream, null);
405
406 int type;
Joe Onorato9bb8fd72009-07-28 18:24:51 -0700407 do {
408 type = parser.next();
409 if (type == XmlPullParser.START_TAG) {
410 String tag = parser.getName();
411 if ("wp".equals(tag)) {
412 mWidth = Integer.parseInt(parser.getAttributeValue(null, "width"));
413 mHeight = Integer.parseInt(parser.getAttributeValue(null, "height"));
414 mName = parser.getAttributeValue(null, "name");
Dianne Hackborn4c62fc02009-08-08 20:40:27 -0700415 String comp = parser.getAttributeValue(null, "component");
416 mWallpaperComponent = comp != null
417 ? ComponentName.unflattenFromString(comp)
418 : null;
Joe Onorato9bb8fd72009-07-28 18:24:51 -0700419 }
420 }
421 } while (type != XmlPullParser.END_DOCUMENT);
422 success = true;
423 } catch (NullPointerException e) {
Joe Onorato2d9c9e32009-07-29 16:43:06 -0700424 Log.w(TAG, "failed parsing " + file + " " + e);
Joe Onorato9bb8fd72009-07-28 18:24:51 -0700425 } catch (NumberFormatException e) {
Joe Onorato2d9c9e32009-07-29 16:43:06 -0700426 Log.w(TAG, "failed parsing " + file + " " + e);
Joe Onorato9bb8fd72009-07-28 18:24:51 -0700427 } catch (XmlPullParserException e) {
Joe Onorato2d9c9e32009-07-29 16:43:06 -0700428 Log.w(TAG, "failed parsing " + file + " " + e);
Joe Onorato9bb8fd72009-07-28 18:24:51 -0700429 } catch (IOException e) {
Joe Onorato2d9c9e32009-07-29 16:43:06 -0700430 Log.w(TAG, "failed parsing " + file + " " + e);
Joe Onorato9bb8fd72009-07-28 18:24:51 -0700431 } catch (IndexOutOfBoundsException e) {
Joe Onorato2d9c9e32009-07-29 16:43:06 -0700432 Log.w(TAG, "failed parsing " + file + " " + e);
Joe Onorato9bb8fd72009-07-28 18:24:51 -0700433 }
434 try {
435 if (stream != null) {
436 stream.close();
437 }
438 } catch (IOException e) {
439 // Ignore
440 }
441
442 if (!success) {
443 mWidth = -1;
444 mHeight = -1;
445 mName = "";
446 }
447 }
448
449 void settingsRestored() {
450 boolean success = false;
451 synchronized (mLock) {
452 loadSettingsLocked();
453 // If there's a wallpaper name, we use that. If that can't be loaded, then we
454 // use the default.
455 if ("".equals(mName)) {
456 success = true;
457 } else {
458 success = restoreNamedResourceLocked();
459 }
460 }
461
462 if (!success) {
463 Log.e(TAG, "Failed to restore wallpaper: '" + mName + "'");
464 mName = "";
465 WALLPAPER_FILE.delete();
466 }
467 saveSettingsLocked();
468 }
469
470 boolean restoreNamedResourceLocked() {
471 if (mName.length() > 4 && "res:".equals(mName.substring(0, 4))) {
472 String resName = mName.substring(4);
473
474 String pkg = null;
475 int colon = resName.indexOf(':');
476 if (colon > 0) {
477 pkg = resName.substring(0, colon);
478 }
479
480 String ident = null;
481 int slash = resName.lastIndexOf('/');
482 if (slash > 0) {
483 ident = resName.substring(slash+1);
484 }
485
486 String type = null;
487 if (colon > 0 && slash > 0 && (slash-colon) > 1) {
488 type = resName.substring(colon+1, slash);
489 }
490
491 if (pkg != null && ident != null && type != null) {
492 int resId = -1;
493 InputStream res = null;
494 FileOutputStream fos = null;
495 try {
496 Context c = mContext.createPackageContext(pkg, Context.CONTEXT_RESTRICTED);
497 Resources r = c.getResources();
498 resId = r.getIdentifier(resName, null, null);
499 if (resId == 0) {
500 Log.e(TAG, "couldn't resolve identifier pkg=" + pkg + " type=" + type
501 + " ident=" + ident);
502 return false;
503 }
504
505 res = r.openRawResource(resId);
506 fos = new FileOutputStream(WALLPAPER_FILE);
507
508 byte[] buffer = new byte[32768];
509 int amt;
510 while ((amt=res.read(buffer)) > 0) {
511 fos.write(buffer, 0, amt);
512 }
513 // mWallpaperObserver will notice the close and send the change broadcast
514
515 Log.d(TAG, "Restored wallpaper: " + resName);
516 return true;
517 } catch (NameNotFoundException e) {
518 Log.e(TAG, "Package name " + pkg + " not found");
519 } catch (Resources.NotFoundException e) {
520 Log.e(TAG, "Resource not found: " + resId);
521 } catch (IOException e) {
522 Log.e(TAG, "IOException while restoring wallpaper ", e);
523 } finally {
524 if (res != null) {
525 try {
526 res.close();
527 } catch (IOException ex) {}
528 }
529 if (fos != null) {
530 try {
531 fos.close();
532 } catch (IOException ex) {}
533 }
534 }
535 }
536 }
537 return false;
538 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800539}