blob: 11981dfdd5c1890cf64d380a9455d777056182ab [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 Onorato9bb8fd72009-07-28 18:24:51 -070079 synchronized (mLock) {
80 if (path == null) {
81 return;
82 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080083
Joe Onorato9bb8fd72009-07-28 18:24:51 -070084 // changing the wallpaper means we'll need to back up the new one
85 long origId = Binder.clearCallingIdentity();
86 BackupManager bm = new BackupManager(mContext);
87 bm.dataChanged();
88 Binder.restoreCallingIdentity(origId);
89
90 File changedFile = new File(WALLPAPER_DIR, path);
91 if (WALLPAPER_FILE.equals(changedFile)) {
92 notifyCallbacksLocked();
93 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080094 }
95 }
96 };
97
98 private final Context mContext;
99
100 private int mWidth = -1;
101 private int mHeight = -1;
Joe Onorato9bb8fd72009-07-28 18:24:51 -0700102 private String mName = "";
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800103
104 public WallpaperService(Context context) {
105 if (Config.LOGD) Log.d(TAG, "WallpaperService startup");
106 mContext = context;
Joe Onorato9bb8fd72009-07-28 18:24:51 -0700107 if (!WALLPAPER_DIR.exists()) {
108 WALLPAPER_DIR.mkdirs();
109 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800110 mWallpaperObserver.startWatching();
Joe Onorato9bb8fd72009-07-28 18:24:51 -0700111 loadSettingsLocked();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800112 }
113
114 @Override
115 protected void finalize() throws Throwable {
116 super.finalize();
117 mWallpaperObserver.stopWatching();
118 }
119
120 public void clearWallpaper() {
Joe Onorato9bb8fd72009-07-28 18:24:51 -0700121 synchronized (mLock) {
122 File f = WALLPAPER_FILE;
123 if (f.exists()) {
124 f.delete();
125 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800126 }
127 }
128
129 public void setDimensionHints(int width, int height) throws RemoteException {
130 checkPermission(android.Manifest.permission.SET_WALLPAPER_HINTS);
131
132 if (width <= 0 || height <= 0) {
133 throw new IllegalArgumentException("width and height must be > 0");
134 }
135
Joe Onorato9bb8fd72009-07-28 18:24:51 -0700136 synchronized (mLock) {
137 if (width != mWidth || height != mHeight) {
138 mWidth = width;
139 mHeight = height;
140 saveSettingsLocked();
141 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800142 }
143 }
144
145 public int getWidthHint() throws RemoteException {
Joe Onorato9bb8fd72009-07-28 18:24:51 -0700146 synchronized (mLock) {
147 return mWidth;
148 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800149 }
150
151 public int getHeightHint() throws RemoteException {
Joe Onorato9bb8fd72009-07-28 18:24:51 -0700152 synchronized (mLock) {
153 return mHeight;
154 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800155 }
156
157 public ParcelFileDescriptor getWallpaper(IWallpaperServiceCallback cb) {
Joe Onorato9bb8fd72009-07-28 18:24:51 -0700158 synchronized (mLock) {
159 try {
160 mCallbacks.register(cb);
161 File f = WALLPAPER_FILE;
162 if (!f.exists()) {
163 return null;
164 }
165 return ParcelFileDescriptor.open(f, MODE_READ_ONLY);
166 } catch (FileNotFoundException e) {
167 /* Shouldn't happen as we check to see if the file exists */
168 if (Config.LOGD) Log.d(TAG, "Error getting wallpaper", e);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800169 }
Joe Onorato9bb8fd72009-07-28 18:24:51 -0700170 return null;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800171 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800172 }
173
Joe Onorato9bb8fd72009-07-28 18:24:51 -0700174 public ParcelFileDescriptor setWallpaper(String name) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800175 checkPermission(android.Manifest.permission.SET_WALLPAPER);
Joe Onorato9bb8fd72009-07-28 18:24:51 -0700176 synchronized (mLock) {
177 if (name == null) name = "";
178 mName = name;
179 saveSettingsLocked();
180 try {
181 ParcelFileDescriptor fd = ParcelFileDescriptor.open(WALLPAPER_FILE,
182 MODE_CREATE|MODE_READ_WRITE);
183 return fd;
184 } catch (FileNotFoundException e) {
185 if (Config.LOGD) Log.d(TAG, "Error setting wallpaper", e);
186 }
187 return null;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800188 }
189 }
190
Joe Onorato9bb8fd72009-07-28 18:24:51 -0700191 private void notifyCallbacksLocked() {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800192 final int n = mCallbacks.beginBroadcast();
193 for (int i = 0; i < n; i++) {
194 try {
195 mCallbacks.getBroadcastItem(i).onWallpaperChanged();
196 } catch (RemoteException e) {
197
198 // The RemoteCallbackList will take care of removing
199 // the dead object for us.
200 }
201 }
202 mCallbacks.finishBroadcast();
203 final Intent intent = new Intent(Intent.ACTION_WALLPAPER_CHANGED);
204 mContext.sendBroadcast(intent);
205 }
206
207 private void checkPermission(String permission) {
Joe Onorato9bb8fd72009-07-28 18:24:51 -0700208 if (PackageManager.PERMISSION_GRANTED!= mContext.checkCallingOrSelfPermission(permission)) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800209 throw new SecurityException("Access denied to process: " + Binder.getCallingPid()
210 + ", must have permission " + permission);
211 }
212 }
Joe Onorato9bb8fd72009-07-28 18:24:51 -0700213
214 private static JournaledFile makeJournaledFile() {
215 final String base = "/data/system/wallpaper_info.xml";
216 return new JournaledFile(new File(base), new File(base + ".tmp"));
217 }
218
219 private void saveSettingsLocked() {
220 JournaledFile journal = makeJournaledFile();
221 FileOutputStream stream = null;
222 try {
223 stream = new FileOutputStream(journal.chooseForWrite(), false);
224 XmlSerializer out = new FastXmlSerializer();
225 out.setOutput(stream, "utf-8");
226 out.startDocument(null, true);
227
228 out.startTag(null, "wp");
229 out.attribute(null, "width", Integer.toString(mWidth));
230 out.attribute(null, "height", Integer.toString(mHeight));
231 out.attribute(null, "name", mName);
232 out.endTag(null, "wp");
233
234 out.endDocument();
235 stream.close();
236 journal.commit();
237 } catch (IOException e) {
238 try {
239 if (stream != null) {
240 stream.close();
241 }
242 } catch (IOException ex) {
243 // Ignore
244 }
245 journal.rollback();
246 }
247 }
248
249 private void loadSettingsLocked() {
250 JournaledFile journal = makeJournaledFile();
251 FileInputStream stream = null;
252 File file = journal.chooseForRead();
253 boolean success = false;
254 try {
255 stream = new FileInputStream(file);
256 XmlPullParser parser = Xml.newPullParser();
257 parser.setInput(stream, null);
258
259 int type;
260 int providerIndex = 0;
261 do {
262 type = parser.next();
263 if (type == XmlPullParser.START_TAG) {
264 String tag = parser.getName();
265 if ("wp".equals(tag)) {
266 mWidth = Integer.parseInt(parser.getAttributeValue(null, "width"));
267 mHeight = Integer.parseInt(parser.getAttributeValue(null, "height"));
268 mName = parser.getAttributeValue(null, "name");
269 }
270 }
271 } while (type != XmlPullParser.END_DOCUMENT);
272 success = true;
273 } catch (NullPointerException e) {
274 Log.w(TAG, "failed parsing " + file, e);
275 } catch (NumberFormatException e) {
276 Log.w(TAG, "failed parsing " + file, e);
277 } catch (XmlPullParserException e) {
278 Log.w(TAG, "failed parsing " + file, e);
279 } catch (IOException e) {
280 Log.w(TAG, "failed parsing " + file, e);
281 } catch (IndexOutOfBoundsException e) {
282 Log.w(TAG, "failed parsing " + file, e);
283 }
284 try {
285 if (stream != null) {
286 stream.close();
287 }
288 } catch (IOException e) {
289 // Ignore
290 }
291
292 if (!success) {
293 mWidth = -1;
294 mHeight = -1;
295 mName = "";
296 }
297 }
298
299 void settingsRestored() {
300 boolean success = false;
301 synchronized (mLock) {
302 loadSettingsLocked();
303 // If there's a wallpaper name, we use that. If that can't be loaded, then we
304 // use the default.
305 if ("".equals(mName)) {
306 success = true;
307 } else {
308 success = restoreNamedResourceLocked();
309 }
310 }
311
312 if (!success) {
313 Log.e(TAG, "Failed to restore wallpaper: '" + mName + "'");
314 mName = "";
315 WALLPAPER_FILE.delete();
316 }
317 saveSettingsLocked();
318 }
319
320 boolean restoreNamedResourceLocked() {
321 if (mName.length() > 4 && "res:".equals(mName.substring(0, 4))) {
322 String resName = mName.substring(4);
323
324 String pkg = null;
325 int colon = resName.indexOf(':');
326 if (colon > 0) {
327 pkg = resName.substring(0, colon);
328 }
329
330 String ident = null;
331 int slash = resName.lastIndexOf('/');
332 if (slash > 0) {
333 ident = resName.substring(slash+1);
334 }
335
336 String type = null;
337 if (colon > 0 && slash > 0 && (slash-colon) > 1) {
338 type = resName.substring(colon+1, slash);
339 }
340
341 if (pkg != null && ident != null && type != null) {
342 int resId = -1;
343 InputStream res = null;
344 FileOutputStream fos = null;
345 try {
346 Context c = mContext.createPackageContext(pkg, Context.CONTEXT_RESTRICTED);
347 Resources r = c.getResources();
348 resId = r.getIdentifier(resName, null, null);
349 if (resId == 0) {
350 Log.e(TAG, "couldn't resolve identifier pkg=" + pkg + " type=" + type
351 + " ident=" + ident);
352 return false;
353 }
354
355 res = r.openRawResource(resId);
356 fos = new FileOutputStream(WALLPAPER_FILE);
357
358 byte[] buffer = new byte[32768];
359 int amt;
360 while ((amt=res.read(buffer)) > 0) {
361 fos.write(buffer, 0, amt);
362 }
363 // mWallpaperObserver will notice the close and send the change broadcast
364
365 Log.d(TAG, "Restored wallpaper: " + resName);
366 return true;
367 } catch (NameNotFoundException e) {
368 Log.e(TAG, "Package name " + pkg + " not found");
369 } catch (Resources.NotFoundException e) {
370 Log.e(TAG, "Resource not found: " + resId);
371 } catch (IOException e) {
372 Log.e(TAG, "IOException while restoring wallpaper ", e);
373 } finally {
374 if (res != null) {
375 try {
376 res.close();
377 } catch (IOException ex) {}
378 }
379 if (fos != null) {
380 try {
381 fos.close();
382 } catch (IOException ex) {}
383 }
384 }
385 }
386 }
387 return false;
388 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800389}