blob: 67b60a63d4e91da7fe2f17cdf7064bb47471203e [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
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080022import android.app.IWallpaperService;
23import android.app.IWallpaperServiceCallback;
Christopher Tate111bd4a2009-06-24 17:29:38 -070024import android.backup.BackupManager;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080025import android.content.Context;
26import android.content.Intent;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080027import android.content.pm.PackageManager;
Joe Onorato9bb8fd72009-07-28 18:24:51 -070028import android.content.pm.PackageManager.NameNotFoundException;
29import android.content.res.Resources;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080030import android.os.Binder;
31import android.os.RemoteException;
32import android.os.FileObserver;
33import android.os.ParcelFileDescriptor;
34import android.os.RemoteCallbackList;
35import android.util.Config;
36import android.util.Log;
Joe Onorato9bb8fd72009-07-28 18:24:51 -070037import android.util.Xml;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080038
Joe Onorato9bb8fd72009-07-28 18:24:51 -070039import java.io.IOException;
40import java.io.InputStream;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080041import java.io.File;
42import java.io.FileNotFoundException;
Joe Onorato9bb8fd72009-07-28 18:24:51 -070043import java.io.FileInputStream;
44import java.io.FileOutputStream;
45
46import org.xmlpull.v1.XmlPullParser;
47import org.xmlpull.v1.XmlPullParserException;
48import org.xmlpull.v1.XmlSerializer;
49
50import com.android.internal.util.FastXmlSerializer;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080051
52class WallpaperService extends IWallpaperService.Stub {
Joe Onorato9bb8fd72009-07-28 18:24:51 -070053 private static final String TAG = "WallpaperService";
54
55 private Object mLock = new Object();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080056
57 private static final File WALLPAPER_DIR = new File(
58 "/data/data/com.android.settings/files");
59 private static final String WALLPAPER = "wallpaper";
60 private static final File WALLPAPER_FILE = new File(WALLPAPER_DIR, WALLPAPER);
61
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080062 /**
63 * List of callbacks registered they should each be notified
64 * when the wallpaper is changed.
65 */
66 private final RemoteCallbackList<IWallpaperServiceCallback> mCallbacks
67 = new RemoteCallbackList<IWallpaperServiceCallback>();
Joe Onorato9bb8fd72009-07-28 18:24:51 -070068
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080069 /**
70 * Observes the wallpaper for changes and notifies all IWallpaperServiceCallbacks
71 * that the wallpaper has changed. The CREATE is triggered when there is no
72 * wallpaper set and is created for the first time. The CLOSE_WRITE is triggered
73 * everytime the wallpaper is changed.
74 */
75 private final FileObserver mWallpaperObserver = new FileObserver(
Joe Onorato9bb8fd72009-07-28 18:24:51 -070076 WALLPAPER_DIR.getAbsolutePath(), CREATE | CLOSE_WRITE | DELETE | DELETE_SELF) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080077 @Override
78 public void onEvent(int event, String path) {
Joe Onoratoe712ee32009-07-29 16:23:58 -070079 if (path == null) {
80 return;
81 }
Joe Onorato9bb8fd72009-07-28 18:24:51 -070082 synchronized (mLock) {
Joe Onorato9bb8fd72009-07-28 18:24:51 -070083 // changing the wallpaper means we'll need to back up the new one
84 long origId = Binder.clearCallingIdentity();
85 BackupManager bm = new BackupManager(mContext);
86 bm.dataChanged();
87 Binder.restoreCallingIdentity(origId);
88
89 File changedFile = new File(WALLPAPER_DIR, path);
90 if (WALLPAPER_FILE.equals(changedFile)) {
91 notifyCallbacksLocked();
92 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080093 }
94 }
95 };
96
97 private final Context mContext;
98
99 private int mWidth = -1;
100 private int mHeight = -1;
Joe Onorato9bb8fd72009-07-28 18:24:51 -0700101 private String mName = "";
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800102
103 public WallpaperService(Context context) {
104 if (Config.LOGD) Log.d(TAG, "WallpaperService startup");
105 mContext = context;
Joe Onoratoe712ee32009-07-29 16:23:58 -0700106 WALLPAPER_DIR.mkdirs();
Joe Onorato9bb8fd72009-07-28 18:24:51 -0700107 loadSettingsLocked();
Joe Onoratoe712ee32009-07-29 16:23:58 -0700108 mWallpaperObserver.startWatching();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800109 }
110
111 @Override
112 protected void finalize() throws Throwable {
113 super.finalize();
114 mWallpaperObserver.stopWatching();
115 }
116
117 public void clearWallpaper() {
Joe Onorato9bb8fd72009-07-28 18:24:51 -0700118 synchronized (mLock) {
119 File f = WALLPAPER_FILE;
120 if (f.exists()) {
121 f.delete();
122 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800123 }
124 }
125
126 public void setDimensionHints(int width, int height) throws RemoteException {
127 checkPermission(android.Manifest.permission.SET_WALLPAPER_HINTS);
128
129 if (width <= 0 || height <= 0) {
130 throw new IllegalArgumentException("width and height must be > 0");
131 }
132
Joe Onorato9bb8fd72009-07-28 18:24:51 -0700133 synchronized (mLock) {
134 if (width != mWidth || height != mHeight) {
135 mWidth = width;
136 mHeight = height;
137 saveSettingsLocked();
138 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800139 }
140 }
141
142 public int getWidthHint() throws RemoteException {
Joe Onorato9bb8fd72009-07-28 18:24:51 -0700143 synchronized (mLock) {
144 return mWidth;
145 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800146 }
147
148 public int getHeightHint() throws RemoteException {
Joe Onorato9bb8fd72009-07-28 18:24:51 -0700149 synchronized (mLock) {
150 return mHeight;
151 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800152 }
153
154 public ParcelFileDescriptor getWallpaper(IWallpaperServiceCallback cb) {
Joe Onorato9bb8fd72009-07-28 18:24:51 -0700155 synchronized (mLock) {
156 try {
157 mCallbacks.register(cb);
158 File f = WALLPAPER_FILE;
159 if (!f.exists()) {
160 return null;
161 }
162 return ParcelFileDescriptor.open(f, MODE_READ_ONLY);
163 } catch (FileNotFoundException e) {
164 /* Shouldn't happen as we check to see if the file exists */
165 if (Config.LOGD) Log.d(TAG, "Error getting wallpaper", e);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800166 }
Joe Onorato9bb8fd72009-07-28 18:24:51 -0700167 return null;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800168 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800169 }
170
Joe Onorato9bb8fd72009-07-28 18:24:51 -0700171 public ParcelFileDescriptor setWallpaper(String name) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800172 checkPermission(android.Manifest.permission.SET_WALLPAPER);
Joe Onorato9bb8fd72009-07-28 18:24:51 -0700173 synchronized (mLock) {
174 if (name == null) name = "";
175 mName = name;
176 saveSettingsLocked();
177 try {
178 ParcelFileDescriptor fd = ParcelFileDescriptor.open(WALLPAPER_FILE,
179 MODE_CREATE|MODE_READ_WRITE);
180 return fd;
181 } catch (FileNotFoundException e) {
182 if (Config.LOGD) Log.d(TAG, "Error setting wallpaper", e);
183 }
184 return null;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800185 }
186 }
187
Joe Onorato9bb8fd72009-07-28 18:24:51 -0700188 private void notifyCallbacksLocked() {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800189 final int n = mCallbacks.beginBroadcast();
190 for (int i = 0; i < n; i++) {
191 try {
192 mCallbacks.getBroadcastItem(i).onWallpaperChanged();
193 } catch (RemoteException e) {
194
195 // The RemoteCallbackList will take care of removing
196 // the dead object for us.
197 }
198 }
199 mCallbacks.finishBroadcast();
200 final Intent intent = new Intent(Intent.ACTION_WALLPAPER_CHANGED);
201 mContext.sendBroadcast(intent);
202 }
203
204 private void checkPermission(String permission) {
Joe Onorato9bb8fd72009-07-28 18:24:51 -0700205 if (PackageManager.PERMISSION_GRANTED!= mContext.checkCallingOrSelfPermission(permission)) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800206 throw new SecurityException("Access denied to process: " + Binder.getCallingPid()
207 + ", must have permission " + permission);
208 }
209 }
Joe Onorato9bb8fd72009-07-28 18:24:51 -0700210
211 private static JournaledFile makeJournaledFile() {
212 final String base = "/data/system/wallpaper_info.xml";
213 return new JournaledFile(new File(base), new File(base + ".tmp"));
214 }
215
216 private void saveSettingsLocked() {
217 JournaledFile journal = makeJournaledFile();
218 FileOutputStream stream = null;
219 try {
220 stream = new FileOutputStream(journal.chooseForWrite(), false);
221 XmlSerializer out = new FastXmlSerializer();
222 out.setOutput(stream, "utf-8");
223 out.startDocument(null, true);
224
225 out.startTag(null, "wp");
226 out.attribute(null, "width", Integer.toString(mWidth));
227 out.attribute(null, "height", Integer.toString(mHeight));
228 out.attribute(null, "name", mName);
229 out.endTag(null, "wp");
230
231 out.endDocument();
232 stream.close();
233 journal.commit();
234 } catch (IOException e) {
235 try {
236 if (stream != null) {
237 stream.close();
238 }
239 } catch (IOException ex) {
240 // Ignore
241 }
242 journal.rollback();
243 }
244 }
245
246 private void loadSettingsLocked() {
247 JournaledFile journal = makeJournaledFile();
248 FileInputStream stream = null;
249 File file = journal.chooseForRead();
250 boolean success = false;
251 try {
252 stream = new FileInputStream(file);
253 XmlPullParser parser = Xml.newPullParser();
254 parser.setInput(stream, null);
255
256 int type;
257 int providerIndex = 0;
258 do {
259 type = parser.next();
260 if (type == XmlPullParser.START_TAG) {
261 String tag = parser.getName();
262 if ("wp".equals(tag)) {
263 mWidth = Integer.parseInt(parser.getAttributeValue(null, "width"));
264 mHeight = Integer.parseInt(parser.getAttributeValue(null, "height"));
265 mName = parser.getAttributeValue(null, "name");
266 }
267 }
268 } while (type != XmlPullParser.END_DOCUMENT);
269 success = true;
270 } catch (NullPointerException e) {
Joe Onorato2d9c9e32009-07-29 16:43:06 -0700271 Log.w(TAG, "failed parsing " + file + " " + e);
Joe Onorato9bb8fd72009-07-28 18:24:51 -0700272 } catch (NumberFormatException e) {
Joe Onorato2d9c9e32009-07-29 16:43:06 -0700273 Log.w(TAG, "failed parsing " + file + " " + e);
Joe Onorato9bb8fd72009-07-28 18:24:51 -0700274 } catch (XmlPullParserException e) {
Joe Onorato2d9c9e32009-07-29 16:43:06 -0700275 Log.w(TAG, "failed parsing " + file + " " + e);
Joe Onorato9bb8fd72009-07-28 18:24:51 -0700276 } catch (IOException e) {
Joe Onorato2d9c9e32009-07-29 16:43:06 -0700277 Log.w(TAG, "failed parsing " + file + " " + e);
Joe Onorato9bb8fd72009-07-28 18:24:51 -0700278 } catch (IndexOutOfBoundsException e) {
Joe Onorato2d9c9e32009-07-29 16:43:06 -0700279 Log.w(TAG, "failed parsing " + file + " " + e);
Joe Onorato9bb8fd72009-07-28 18:24:51 -0700280 }
281 try {
282 if (stream != null) {
283 stream.close();
284 }
285 } catch (IOException e) {
286 // Ignore
287 }
288
289 if (!success) {
290 mWidth = -1;
291 mHeight = -1;
292 mName = "";
293 }
294 }
295
296 void settingsRestored() {
297 boolean success = false;
298 synchronized (mLock) {
299 loadSettingsLocked();
300 // If there's a wallpaper name, we use that. If that can't be loaded, then we
301 // use the default.
302 if ("".equals(mName)) {
303 success = true;
304 } else {
305 success = restoreNamedResourceLocked();
306 }
307 }
308
309 if (!success) {
310 Log.e(TAG, "Failed to restore wallpaper: '" + mName + "'");
311 mName = "";
312 WALLPAPER_FILE.delete();
313 }
314 saveSettingsLocked();
315 }
316
317 boolean restoreNamedResourceLocked() {
318 if (mName.length() > 4 && "res:".equals(mName.substring(0, 4))) {
319 String resName = mName.substring(4);
320
321 String pkg = null;
322 int colon = resName.indexOf(':');
323 if (colon > 0) {
324 pkg = resName.substring(0, colon);
325 }
326
327 String ident = null;
328 int slash = resName.lastIndexOf('/');
329 if (slash > 0) {
330 ident = resName.substring(slash+1);
331 }
332
333 String type = null;
334 if (colon > 0 && slash > 0 && (slash-colon) > 1) {
335 type = resName.substring(colon+1, slash);
336 }
337
338 if (pkg != null && ident != null && type != null) {
339 int resId = -1;
340 InputStream res = null;
341 FileOutputStream fos = null;
342 try {
343 Context c = mContext.createPackageContext(pkg, Context.CONTEXT_RESTRICTED);
344 Resources r = c.getResources();
345 resId = r.getIdentifier(resName, null, null);
346 if (resId == 0) {
347 Log.e(TAG, "couldn't resolve identifier pkg=" + pkg + " type=" + type
348 + " ident=" + ident);
349 return false;
350 }
351
352 res = r.openRawResource(resId);
353 fos = new FileOutputStream(WALLPAPER_FILE);
354
355 byte[] buffer = new byte[32768];
356 int amt;
357 while ((amt=res.read(buffer)) > 0) {
358 fos.write(buffer, 0, amt);
359 }
360 // mWallpaperObserver will notice the close and send the change broadcast
361
362 Log.d(TAG, "Restored wallpaper: " + resName);
363 return true;
364 } catch (NameNotFoundException e) {
365 Log.e(TAG, "Package name " + pkg + " not found");
366 } catch (Resources.NotFoundException e) {
367 Log.e(TAG, "Resource not found: " + resId);
368 } catch (IOException e) {
369 Log.e(TAG, "IOException while restoring wallpaper ", e);
370 } finally {
371 if (res != null) {
372 try {
373 res.close();
374 } catch (IOException ex) {}
375 }
376 if (fos != null) {
377 try {
378 fos.close();
379 } catch (IOException ex) {}
380 }
381 }
382 }
383 }
384 return false;
385 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800386}