PageRenderTime 117ms CodeModel.GetById 13ms app.highlight 92ms RepoModel.GetById 1ms app.codeStats 1ms

/services/java/com/android/server/WallpaperManagerService.java

https://github.com/aizuzi/platform_frameworks_base
Java | 1345 lines | 1145 code | 106 blank | 94 comment | 260 complexity | 2a2875d2135aec24947a9d7ecfc88417 MD5 | raw file

Large files files are truncated, but you can click here to view the full file

   1/*
   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.ParcelFileDescriptor.*;
  20
  21import android.app.ActivityManagerNative;
  22import android.app.AppGlobals;
  23import android.app.IUserSwitchObserver;
  24import android.app.IWallpaperManager;
  25import android.app.IWallpaperManagerCallback;
  26import android.app.PendingIntent;
  27import android.app.WallpaperInfo;
  28import android.app.backup.BackupManager;
  29import android.app.backup.WallpaperBackupHelper;
  30import android.content.BroadcastReceiver;
  31import android.content.ComponentName;
  32import android.content.Context;
  33import android.content.Intent;
  34import android.content.IntentFilter;
  35import android.content.ServiceConnection;
  36import android.content.pm.IPackageManager;
  37import android.content.pm.PackageManager;
  38import android.content.pm.ResolveInfo;
  39import android.content.pm.ServiceInfo;
  40import android.content.pm.PackageManager.NameNotFoundException;
  41import android.content.pm.UserInfo;
  42import android.content.res.Resources;
  43import android.graphics.Point;
  44import android.os.Binder;
  45import android.os.Bundle;
  46import android.os.Environment;
  47import android.os.FileUtils;
  48import android.os.IBinder;
  49import android.os.IRemoteCallback;
  50import android.os.RemoteException;
  51import android.os.FileObserver;
  52import android.os.ParcelFileDescriptor;
  53import android.os.RemoteCallbackList;
  54import android.os.SELinux;
  55import android.os.ServiceManager;
  56import android.os.SystemClock;
  57import android.os.UserHandle;
  58import android.os.UserManager;
  59import android.service.wallpaper.IWallpaperConnection;
  60import android.service.wallpaper.IWallpaperEngine;
  61import android.service.wallpaper.IWallpaperService;
  62import android.service.wallpaper.WallpaperService;
  63import android.util.Slog;
  64import android.util.SparseArray;
  65import android.util.Xml;
  66import android.view.Display;
  67import android.view.IWindowManager;
  68import android.view.WindowManager;
  69
  70import java.io.FileDescriptor;
  71import java.io.IOException;
  72import java.io.InputStream;
  73import java.io.File;
  74import java.io.FileNotFoundException;
  75import java.io.FileInputStream;
  76import java.io.FileOutputStream;
  77import java.io.PrintWriter;
  78import java.util.List;
  79
  80import org.xmlpull.v1.XmlPullParser;
  81import org.xmlpull.v1.XmlPullParserException;
  82import org.xmlpull.v1.XmlSerializer;
  83
  84import com.android.internal.content.PackageMonitor;
  85import com.android.internal.util.FastXmlSerializer;
  86import com.android.internal.util.JournaledFile;
  87
  88class WallpaperManagerService extends IWallpaperManager.Stub {
  89    static final String TAG = "WallpaperService";
  90    static final boolean DEBUG = false;
  91
  92    final Object mLock = new Object[0];
  93
  94    /**
  95     * Minimum time between crashes of a wallpaper service for us to consider
  96     * restarting it vs. just reverting to the static wallpaper.
  97     */
  98    static final long MIN_WALLPAPER_CRASH_TIME = 10000;
  99    static final String WALLPAPER = "wallpaper";
 100    static final String WALLPAPER_INFO = "wallpaper_info.xml";
 101
 102    /**
 103     * Name of the component used to display bitmap wallpapers from either the gallery or
 104     * built-in wallpapers.
 105     */
 106    static final ComponentName IMAGE_WALLPAPER = new ComponentName("com.android.systemui",
 107            "com.android.systemui.ImageWallpaper");
 108
 109    /**
 110     * Observes the wallpaper for changes and notifies all IWallpaperServiceCallbacks
 111     * that the wallpaper has changed. The CREATE is triggered when there is no
 112     * wallpaper set and is created for the first time. The CLOSE_WRITE is triggered
 113     * everytime the wallpaper is changed.
 114     */
 115    private class WallpaperObserver extends FileObserver {
 116
 117        final WallpaperData mWallpaper;
 118        final File mWallpaperDir;
 119        final File mWallpaperFile;
 120
 121        public WallpaperObserver(WallpaperData wallpaper) {
 122            super(getWallpaperDir(wallpaper.userId).getAbsolutePath(),
 123                    CLOSE_WRITE | DELETE | DELETE_SELF);
 124            mWallpaperDir = getWallpaperDir(wallpaper.userId);
 125            mWallpaper = wallpaper;
 126            mWallpaperFile = new File(mWallpaperDir, WALLPAPER);
 127        }
 128
 129        @Override
 130        public void onEvent(int event, String path) {
 131            if (path == null) {
 132                return;
 133            }
 134            synchronized (mLock) {
 135                // changing the wallpaper means we'll need to back up the new one
 136                long origId = Binder.clearCallingIdentity();
 137                BackupManager bm = new BackupManager(mContext);
 138                bm.dataChanged();
 139                Binder.restoreCallingIdentity(origId);
 140
 141                File changedFile = new File(mWallpaperDir, path);
 142                if (mWallpaperFile.equals(changedFile)) {
 143                    notifyCallbacksLocked(mWallpaper);
 144                    if (mWallpaper.wallpaperComponent == null || event != CLOSE_WRITE
 145                            || mWallpaper.imageWallpaperPending) {
 146                        if (event == CLOSE_WRITE) {
 147                            mWallpaper.imageWallpaperPending = false;
 148                        }
 149                        bindWallpaperComponentLocked(IMAGE_WALLPAPER, true,
 150                                false, mWallpaper, null);
 151                        saveSettingsLocked(mWallpaper);
 152                    }
 153                }
 154            }
 155        }
 156    }
 157
 158    final Context mContext;
 159    final IWindowManager mIWindowManager;
 160    final IPackageManager mIPackageManager;
 161    final MyPackageMonitor mMonitor;
 162    WallpaperData mLastWallpaper;
 163
 164    SparseArray<WallpaperData> mWallpaperMap = new SparseArray<WallpaperData>();
 165
 166    int mCurrentUserId;
 167
 168    static class WallpaperData {
 169
 170        int userId;
 171
 172        File wallpaperFile;
 173
 174        /**
 175         * Client is currently writing a new image wallpaper.
 176         */
 177        boolean imageWallpaperPending;
 178
 179        /**
 180         * Resource name if using a picture from the wallpaper gallery
 181         */
 182        String name = "";
 183
 184        /**
 185         * The component name of the currently set live wallpaper.
 186         */
 187        ComponentName wallpaperComponent;
 188
 189        /**
 190         * The component name of the wallpaper that should be set next.
 191         */
 192        ComponentName nextWallpaperComponent;
 193
 194        WallpaperConnection connection;
 195        long lastDiedTime;
 196        boolean wallpaperUpdating;
 197        WallpaperObserver wallpaperObserver;
 198
 199        /**
 200         * List of callbacks registered they should each be notified when the wallpaper is changed.
 201         */
 202        private RemoteCallbackList<IWallpaperManagerCallback> callbacks
 203                = new RemoteCallbackList<IWallpaperManagerCallback>();
 204
 205        int width = -1;
 206        int height = -1;
 207
 208        WallpaperData(int userId) {
 209            this.userId = userId;
 210            wallpaperFile = new File(getWallpaperDir(userId), WALLPAPER);
 211        }
 212    }
 213
 214    class WallpaperConnection extends IWallpaperConnection.Stub
 215            implements ServiceConnection {
 216        final WallpaperInfo mInfo;
 217        final Binder mToken = new Binder();
 218        IWallpaperService mService;
 219        IWallpaperEngine mEngine;
 220        WallpaperData mWallpaper;
 221        IRemoteCallback mReply;
 222
 223        boolean mDimensionsChanged = false;
 224
 225        public WallpaperConnection(WallpaperInfo info, WallpaperData wallpaper) {
 226            mInfo = info;
 227            mWallpaper = wallpaper;
 228        }
 229
 230        @Override
 231        public void onServiceConnected(ComponentName name, IBinder service) {
 232            synchronized (mLock) {
 233                if (mWallpaper.connection == this) {
 234                    mWallpaper.lastDiedTime = SystemClock.uptimeMillis();
 235                    mService = IWallpaperService.Stub.asInterface(service);
 236                    attachServiceLocked(this, mWallpaper);
 237                    // XXX should probably do saveSettingsLocked() later
 238                    // when we have an engine, but I'm not sure about
 239                    // locking there and anyway we always need to be able to
 240                    // recover if there is something wrong.
 241                    saveSettingsLocked(mWallpaper);
 242                }
 243            }
 244        }
 245
 246        @Override
 247        public void onServiceDisconnected(ComponentName name) {
 248            synchronized (mLock) {
 249                mService = null;
 250                mEngine = null;
 251                if (mWallpaper.connection == this) {
 252                    Slog.w(TAG, "Wallpaper service gone: " + mWallpaper.wallpaperComponent);
 253                    if (!mWallpaper.wallpaperUpdating
 254                            && (mWallpaper.lastDiedTime + MIN_WALLPAPER_CRASH_TIME)
 255                                > SystemClock.uptimeMillis()
 256                            && mWallpaper.userId == mCurrentUserId) {
 257                        Slog.w(TAG, "Reverting to built-in wallpaper!");
 258                        clearWallpaperLocked(true, mWallpaper.userId, null);
 259                    }
 260                }
 261            }
 262        }
 263
 264        @Override
 265        public void attachEngine(IWallpaperEngine engine) {
 266            synchronized (mLock) {
 267                mEngine = engine;
 268                if (mDimensionsChanged) {
 269                    try {
 270                        mEngine.setDesiredSize(mWallpaper.width, mWallpaper.height);
 271                    } catch (RemoteException e) {
 272                        Slog.w(TAG, "Failed to set wallpaper dimensions", e);
 273                    }
 274                    mDimensionsChanged = false;
 275                }
 276            }
 277        }
 278
 279        @Override
 280        public void engineShown(IWallpaperEngine engine) {
 281            synchronized (mLock) {
 282                if (mReply != null) {
 283                    long ident = Binder.clearCallingIdentity();
 284                    try {
 285                        mReply.sendResult(null);
 286                    } catch (RemoteException e) {
 287                        Binder.restoreCallingIdentity(ident);
 288                    }
 289                    mReply = null;
 290                }
 291            }
 292        }
 293
 294        @Override
 295        public ParcelFileDescriptor setWallpaper(String name) {
 296            synchronized (mLock) {
 297                if (mWallpaper.connection == this) {
 298                    return updateWallpaperBitmapLocked(name, mWallpaper);
 299                }
 300                return null;
 301            }
 302        }
 303    }
 304
 305    class MyPackageMonitor extends PackageMonitor {
 306        @Override
 307        public void onPackageUpdateFinished(String packageName, int uid) {
 308            synchronized (mLock) {
 309                if (mCurrentUserId != getChangingUserId()) {
 310                    return;
 311                }
 312                WallpaperData wallpaper = mWallpaperMap.get(mCurrentUserId);
 313                if (wallpaper != null) {
 314                    if (wallpaper.wallpaperComponent != null
 315                            && wallpaper.wallpaperComponent.getPackageName().equals(packageName)) {
 316                        wallpaper.wallpaperUpdating = false;
 317                        ComponentName comp = wallpaper.wallpaperComponent;
 318                        clearWallpaperComponentLocked(wallpaper);
 319                        if (!bindWallpaperComponentLocked(comp, false, false,
 320                                wallpaper, null)) {
 321                            Slog.w(TAG, "Wallpaper no longer available; reverting to default");
 322                            clearWallpaperLocked(false, wallpaper.userId, null);
 323                        }
 324                    }
 325                }
 326            }
 327        }
 328
 329        @Override
 330        public void onPackageModified(String packageName) {
 331            synchronized (mLock) {
 332                if (mCurrentUserId != getChangingUserId()) {
 333                    return;
 334                }
 335                WallpaperData wallpaper = mWallpaperMap.get(mCurrentUserId);
 336                if (wallpaper != null) {
 337                    if (wallpaper.wallpaperComponent == null
 338                            || !wallpaper.wallpaperComponent.getPackageName().equals(packageName)) {
 339                        return;
 340                    }
 341                    doPackagesChangedLocked(true, wallpaper);
 342                }
 343            }
 344        }
 345
 346        @Override
 347        public void onPackageUpdateStarted(String packageName, int uid) {
 348            synchronized (mLock) {
 349                if (mCurrentUserId != getChangingUserId()) {
 350                    return;
 351                }
 352                WallpaperData wallpaper = mWallpaperMap.get(mCurrentUserId);
 353                if (wallpaper != null) {
 354                    if (wallpaper.wallpaperComponent != null
 355                            && wallpaper.wallpaperComponent.getPackageName().equals(packageName)) {
 356                        wallpaper.wallpaperUpdating = true;
 357                    }
 358                }
 359            }
 360        }
 361
 362        @Override
 363        public boolean onHandleForceStop(Intent intent, String[] packages, int uid, boolean doit) {
 364            synchronized (mLock) {
 365                boolean changed = false;
 366                if (mCurrentUserId != getChangingUserId()) {
 367                    return false;
 368                }
 369                WallpaperData wallpaper = mWallpaperMap.get(mCurrentUserId);
 370                if (wallpaper != null) {
 371                    boolean res = doPackagesChangedLocked(doit, wallpaper);
 372                    changed |= res;
 373                }
 374                return changed;
 375            }
 376        }
 377
 378        @Override
 379        public void onSomePackagesChanged() {
 380            synchronized (mLock) {
 381                if (mCurrentUserId != getChangingUserId()) {
 382                    return;
 383                }
 384                WallpaperData wallpaper = mWallpaperMap.get(mCurrentUserId);
 385                if (wallpaper != null) {
 386                    doPackagesChangedLocked(true, wallpaper);
 387                }
 388            }
 389        }
 390
 391        boolean doPackagesChangedLocked(boolean doit, WallpaperData wallpaper) {
 392            boolean changed = false;
 393            if (wallpaper.wallpaperComponent != null) {
 394                int change = isPackageDisappearing(wallpaper.wallpaperComponent
 395                        .getPackageName());
 396                if (change == PACKAGE_PERMANENT_CHANGE
 397                        || change == PACKAGE_TEMPORARY_CHANGE) {
 398                    changed = true;
 399                    if (doit) {
 400                        Slog.w(TAG, "Wallpaper uninstalled, removing: "
 401                                + wallpaper.wallpaperComponent);
 402                        clearWallpaperLocked(false, wallpaper.userId, null);
 403                    }
 404                }
 405            }
 406            if (wallpaper.nextWallpaperComponent != null) {
 407                int change = isPackageDisappearing(wallpaper.nextWallpaperComponent
 408                        .getPackageName());
 409                if (change == PACKAGE_PERMANENT_CHANGE
 410                        || change == PACKAGE_TEMPORARY_CHANGE) {
 411                    wallpaper.nextWallpaperComponent = null;
 412                }
 413            }
 414            if (wallpaper.wallpaperComponent != null
 415                    && isPackageModified(wallpaper.wallpaperComponent.getPackageName())) {
 416                try {
 417                    mContext.getPackageManager().getServiceInfo(
 418                            wallpaper.wallpaperComponent, 0);
 419                } catch (NameNotFoundException e) {
 420                    Slog.w(TAG, "Wallpaper component gone, removing: "
 421                            + wallpaper.wallpaperComponent);
 422                    clearWallpaperLocked(false, wallpaper.userId, null);
 423                }
 424            }
 425            if (wallpaper.nextWallpaperComponent != null
 426                    && isPackageModified(wallpaper.nextWallpaperComponent.getPackageName())) {
 427                try {
 428                    mContext.getPackageManager().getServiceInfo(
 429                            wallpaper.nextWallpaperComponent, 0);
 430                } catch (NameNotFoundException e) {
 431                    wallpaper.nextWallpaperComponent = null;
 432                }
 433            }
 434            return changed;
 435        }
 436    }
 437    
 438    public WallpaperManagerService(Context context) {
 439        if (DEBUG) Slog.v(TAG, "WallpaperService startup");
 440        mContext = context;
 441        mIWindowManager = IWindowManager.Stub.asInterface(
 442                ServiceManager.getService(Context.WINDOW_SERVICE));
 443        mIPackageManager = AppGlobals.getPackageManager();
 444        mMonitor = new MyPackageMonitor();
 445        mMonitor.register(context, null, UserHandle.ALL, true);
 446        getWallpaperDir(UserHandle.USER_OWNER).mkdirs();
 447        loadSettingsLocked(UserHandle.USER_OWNER);
 448    }
 449    
 450    private static File getWallpaperDir(int userId) {
 451        return Environment.getUserSystemDirectory(userId);
 452    }
 453
 454    @Override
 455    protected void finalize() throws Throwable {
 456        super.finalize();
 457        for (int i = 0; i < mWallpaperMap.size(); i++) {
 458            WallpaperData wallpaper = mWallpaperMap.valueAt(i);
 459            wallpaper.wallpaperObserver.stopWatching();
 460        }
 461    }
 462
 463    public void systemRunning() {
 464        if (DEBUG) Slog.v(TAG, "systemReady");
 465        WallpaperData wallpaper = mWallpaperMap.get(UserHandle.USER_OWNER);
 466        switchWallpaper(wallpaper, null);
 467        wallpaper.wallpaperObserver = new WallpaperObserver(wallpaper);
 468        wallpaper.wallpaperObserver.startWatching();
 469
 470        IntentFilter userFilter = new IntentFilter();
 471        userFilter.addAction(Intent.ACTION_USER_REMOVED);
 472        userFilter.addAction(Intent.ACTION_USER_STOPPING);
 473        mContext.registerReceiver(new BroadcastReceiver() {
 474            @Override
 475            public void onReceive(Context context, Intent intent) {
 476                String action = intent.getAction();
 477                if (Intent.ACTION_USER_REMOVED.equals(action)) {
 478                    onRemoveUser(intent.getIntExtra(Intent.EXTRA_USER_HANDLE,
 479                            UserHandle.USER_NULL));
 480                }
 481                // TODO: Race condition causing problems when cleaning up on stopping a user.
 482                // Comment this out for now.
 483                // else if (Intent.ACTION_USER_STOPPING.equals(action)) {
 484                //     onStoppingUser(intent.getIntExtra(Intent.EXTRA_USER_HANDLE,
 485                //             UserHandle.USER_NULL));
 486                // }
 487            }
 488        }, userFilter);
 489
 490        try {
 491            ActivityManagerNative.getDefault().registerUserSwitchObserver(
 492                    new IUserSwitchObserver.Stub() {
 493                        @Override
 494                        public void onUserSwitching(int newUserId, IRemoteCallback reply) {
 495                            switchUser(newUserId, reply);
 496                        }
 497
 498                        @Override
 499                        public void onUserSwitchComplete(int newUserId) throws RemoteException {
 500                        }
 501                    });
 502        } catch (RemoteException e) {
 503            // TODO Auto-generated catch block
 504            e.printStackTrace();
 505        }
 506    }
 507
 508    String getName() {
 509        synchronized (mLock) {
 510            return mWallpaperMap.get(0).name;
 511        }
 512    }
 513
 514    void onStoppingUser(int userId) {
 515        if (userId < 1) return;
 516        synchronized (mLock) {
 517            WallpaperData wallpaper = mWallpaperMap.get(userId);
 518            if (wallpaper != null) {
 519                if (wallpaper.wallpaperObserver != null) {
 520                    wallpaper.wallpaperObserver.stopWatching();
 521                    wallpaper.wallpaperObserver = null;
 522                }
 523                mWallpaperMap.remove(userId);
 524            }
 525        }
 526    }
 527
 528    void onRemoveUser(int userId) {
 529        if (userId < 1) return;
 530        synchronized (mLock) {
 531            onStoppingUser(userId);
 532            File wallpaperFile = new File(getWallpaperDir(userId), WALLPAPER);
 533            wallpaperFile.delete();
 534            File wallpaperInfoFile = new File(getWallpaperDir(userId), WALLPAPER_INFO);
 535            wallpaperInfoFile.delete();
 536        }
 537    }
 538
 539    void switchUser(int userId, IRemoteCallback reply) {
 540        synchronized (mLock) {
 541            mCurrentUserId = userId;
 542            WallpaperData wallpaper = mWallpaperMap.get(userId);
 543            if (wallpaper == null) {
 544                wallpaper = new WallpaperData(userId);
 545                mWallpaperMap.put(userId, wallpaper);
 546                loadSettingsLocked(userId);
 547            }
 548            // Not started watching yet, in case wallpaper data was loaded for other reasons.
 549            if (wallpaper.wallpaperObserver == null) {
 550                wallpaper.wallpaperObserver = new WallpaperObserver(wallpaper);
 551                wallpaper.wallpaperObserver.startWatching();
 552            }
 553            switchWallpaper(wallpaper, reply);
 554        }
 555    }
 556
 557    void switchWallpaper(WallpaperData wallpaper, IRemoteCallback reply) {
 558        synchronized (mLock) {
 559            RuntimeException e = null;
 560            try {
 561                ComponentName cname = wallpaper.wallpaperComponent != null ?
 562                        wallpaper.wallpaperComponent : wallpaper.nextWallpaperComponent;
 563                if (bindWallpaperComponentLocked(cname, true, false, wallpaper, reply)) {
 564                    return;
 565                }
 566            } catch (RuntimeException e1) {
 567                e = e1;
 568            }
 569            Slog.w(TAG, "Failure starting previous wallpaper", e);
 570            clearWallpaperLocked(false, wallpaper.userId, reply);
 571        }
 572    }
 573
 574    public void clearWallpaper() {
 575        if (DEBUG) Slog.v(TAG, "clearWallpaper");
 576        synchronized (mLock) {
 577            clearWallpaperLocked(false, UserHandle.getCallingUserId(), null);
 578        }
 579    }
 580
 581    void clearWallpaperLocked(boolean defaultFailed, int userId, IRemoteCallback reply) {
 582        WallpaperData wallpaper = mWallpaperMap.get(userId);
 583        File f = new File(getWallpaperDir(userId), WALLPAPER);
 584        if (f.exists()) {
 585            f.delete();
 586        }
 587        final long ident = Binder.clearCallingIdentity();
 588        RuntimeException e = null;
 589        try {
 590            wallpaper.imageWallpaperPending = false;
 591            if (userId != mCurrentUserId) return;
 592            if (bindWallpaperComponentLocked(defaultFailed
 593                    ? IMAGE_WALLPAPER
 594                    : null, true, false, wallpaper, reply)) {
 595                return;
 596            }
 597        } catch (IllegalArgumentException e1) {
 598            e = e1;
 599        } finally {
 600            Binder.restoreCallingIdentity(ident);
 601        }
 602        
 603        // This can happen if the default wallpaper component doesn't
 604        // exist.  This should be a system configuration problem, but
 605        // let's not let it crash the system and just live with no
 606        // wallpaper.
 607        Slog.e(TAG, "Default wallpaper component not found!", e);
 608        clearWallpaperComponentLocked(wallpaper);
 609        if (reply != null) {
 610            try {
 611                reply.sendResult(null);
 612            } catch (RemoteException e1) {
 613            }
 614        }
 615    }
 616
 617    public boolean hasNamedWallpaper(String name) {
 618        synchronized (mLock) {
 619            List<UserInfo> users;
 620            long ident = Binder.clearCallingIdentity();
 621            try {
 622                users = ((UserManager) mContext.getSystemService(Context.USER_SERVICE)).getUsers();
 623            } finally {
 624                Binder.restoreCallingIdentity(ident);
 625            }
 626            for (UserInfo user: users) {
 627                WallpaperData wd = mWallpaperMap.get(user.id);
 628                if (wd == null) {
 629                    // User hasn't started yet, so load her settings to peek at the wallpaper
 630                    loadSettingsLocked(user.id);
 631                    wd = mWallpaperMap.get(user.id);
 632                }
 633                if (wd != null && name.equals(wd.name)) {
 634                    return true;
 635                }
 636            }
 637        }
 638        return false;
 639    }
 640
 641    private Point getDefaultDisplaySize() {
 642        Point p = new Point();
 643        WindowManager wm = (WindowManager)mContext.getSystemService(Context.WINDOW_SERVICE);
 644        Display d = wm.getDefaultDisplay();
 645        d.getRealSize(p);
 646        return p;
 647    }
 648
 649    public void setDimensionHints(int width, int height) throws RemoteException {
 650        checkPermission(android.Manifest.permission.SET_WALLPAPER_HINTS);
 651        synchronized (mLock) {
 652            int userId = UserHandle.getCallingUserId();
 653            WallpaperData wallpaper = mWallpaperMap.get(userId);
 654            if (wallpaper == null) {
 655                throw new IllegalStateException("Wallpaper not yet initialized for user " + userId);
 656            }
 657            if (width <= 0 || height <= 0) {
 658                throw new IllegalArgumentException("width and height must be > 0");
 659            }
 660            // Make sure it is at least as large as the display.
 661            Point displaySize = getDefaultDisplaySize();
 662            width = Math.max(width, displaySize.x);
 663            height = Math.max(height, displaySize.y);
 664
 665            if (width != wallpaper.width || height != wallpaper.height) {
 666                wallpaper.width = width;
 667                wallpaper.height = height;
 668                saveSettingsLocked(wallpaper);
 669                if (mCurrentUserId != userId) return; // Don't change the properties now
 670                if (wallpaper.connection != null) {
 671                    if (wallpaper.connection.mEngine != null) {
 672                        try {
 673                            wallpaper.connection.mEngine.setDesiredSize(
 674                                    width, height);
 675                        } catch (RemoteException e) {
 676                        }
 677                        notifyCallbacksLocked(wallpaper);
 678                    } else if (wallpaper.connection.mService != null) {
 679                        // We've attached to the service but the engine hasn't attached back to us
 680                        // yet. This means it will be created with the previous dimensions, so we
 681                        // need to update it to the new dimensions once it attaches.
 682                        wallpaper.connection.mDimensionsChanged = true;
 683                    }
 684                }
 685            }
 686        }
 687    }
 688
 689    public int getWidthHint() throws RemoteException {
 690        synchronized (mLock) {
 691            WallpaperData wallpaper = mWallpaperMap.get(UserHandle.getCallingUserId());
 692            return wallpaper.width;
 693        }
 694    }
 695
 696    public int getHeightHint() throws RemoteException {
 697        synchronized (mLock) {
 698            WallpaperData wallpaper = mWallpaperMap.get(UserHandle.getCallingUserId());
 699            return wallpaper.height;
 700        }
 701    }
 702
 703    public ParcelFileDescriptor getWallpaper(IWallpaperManagerCallback cb,
 704            Bundle outParams) {
 705        synchronized (mLock) {
 706            // This returns the current user's wallpaper, if called by a system service. Else it
 707            // returns the wallpaper for the calling user.
 708            int callingUid = Binder.getCallingUid();
 709            int wallpaperUserId = 0;
 710            if (callingUid == android.os.Process.SYSTEM_UID) {
 711                wallpaperUserId = mCurrentUserId;
 712            } else {
 713                wallpaperUserId = UserHandle.getUserId(callingUid);
 714            }
 715            WallpaperData wallpaper = mWallpaperMap.get(wallpaperUserId);
 716            try {
 717                if (outParams != null) {
 718                    outParams.putInt("width", wallpaper.width);
 719                    outParams.putInt("height", wallpaper.height);
 720                }
 721                wallpaper.callbacks.register(cb);
 722                File f = new File(getWallpaperDir(wallpaperUserId), WALLPAPER);
 723                if (!f.exists()) {
 724                    return null;
 725                }
 726                return ParcelFileDescriptor.open(f, MODE_READ_ONLY);
 727            } catch (FileNotFoundException e) {
 728                /* Shouldn't happen as we check to see if the file exists */
 729                Slog.w(TAG, "Error getting wallpaper", e);
 730            }
 731            return null;
 732        }
 733    }
 734
 735    public WallpaperInfo getWallpaperInfo() {
 736        int userId = UserHandle.getCallingUserId();
 737        synchronized (mLock) {
 738            WallpaperData wallpaper = mWallpaperMap.get(userId);
 739            if (wallpaper.connection != null) {
 740                return wallpaper.connection.mInfo;
 741            }
 742            return null;
 743        }
 744    }
 745
 746    public ParcelFileDescriptor setWallpaper(String name) {
 747        checkPermission(android.Manifest.permission.SET_WALLPAPER);
 748        synchronized (mLock) {
 749            if (DEBUG) Slog.v(TAG, "setWallpaper");
 750            int userId = UserHandle.getCallingUserId();
 751            WallpaperData wallpaper = mWallpaperMap.get(userId);
 752            if (wallpaper == null) {
 753                throw new IllegalStateException("Wallpaper not yet initialized for user " + userId);
 754            }
 755            final long ident = Binder.clearCallingIdentity();
 756            try {
 757                ParcelFileDescriptor pfd = updateWallpaperBitmapLocked(name, wallpaper);
 758                if (pfd != null) {
 759                    wallpaper.imageWallpaperPending = true;
 760                }
 761                return pfd;
 762            } finally {
 763                Binder.restoreCallingIdentity(ident);
 764            }
 765        }
 766    }
 767
 768    ParcelFileDescriptor updateWallpaperBitmapLocked(String name, WallpaperData wallpaper) {
 769        if (name == null) name = "";
 770        try {
 771            File dir = getWallpaperDir(wallpaper.userId);
 772            if (!dir.exists()) {
 773                dir.mkdir();
 774                FileUtils.setPermissions(
 775                        dir.getPath(),
 776                        FileUtils.S_IRWXU|FileUtils.S_IRWXG|FileUtils.S_IXOTH,
 777                        -1, -1);
 778            }
 779            File file = new File(dir, WALLPAPER);
 780            ParcelFileDescriptor fd = ParcelFileDescriptor.open(file,
 781                    MODE_CREATE|MODE_READ_WRITE);
 782            if (!SELinux.restorecon(file)) {
 783                return null;
 784            }
 785            wallpaper.name = name;
 786            return fd;
 787        } catch (FileNotFoundException e) {
 788            Slog.w(TAG, "Error setting wallpaper", e);
 789        }
 790        return null;
 791    }
 792
 793    public void setWallpaperComponent(ComponentName name) {
 794        checkPermission(android.Manifest.permission.SET_WALLPAPER_COMPONENT);
 795        synchronized (mLock) {
 796            if (DEBUG) Slog.v(TAG, "setWallpaperComponent name=" + name);
 797            int userId = UserHandle.getCallingUserId();
 798            WallpaperData wallpaper = mWallpaperMap.get(userId);
 799            if (wallpaper == null) {
 800                throw new IllegalStateException("Wallpaper not yet initialized for user " + userId);
 801            }
 802            final long ident = Binder.clearCallingIdentity();
 803            try {
 804                wallpaper.imageWallpaperPending = false;
 805                bindWallpaperComponentLocked(name, false, true, wallpaper, null);
 806            } finally {
 807                Binder.restoreCallingIdentity(ident);
 808            }
 809        }
 810    }
 811    
 812    boolean bindWallpaperComponentLocked(ComponentName componentName, boolean force,
 813            boolean fromUser, WallpaperData wallpaper, IRemoteCallback reply) {
 814        if (DEBUG) Slog.v(TAG, "bindWallpaperComponentLocked: componentName=" + componentName);
 815        // Has the component changed?
 816        if (!force) {
 817            if (wallpaper.connection != null) {
 818                if (wallpaper.wallpaperComponent == null) {
 819                    if (componentName == null) {
 820                        if (DEBUG) Slog.v(TAG, "bindWallpaperComponentLocked: still using default");
 821                        // Still using default wallpaper.
 822                        return true;
 823                    }
 824                } else if (wallpaper.wallpaperComponent.equals(componentName)) {
 825                    // Changing to same wallpaper.
 826                    if (DEBUG) Slog.v(TAG, "same wallpaper");
 827                    return true;
 828                }
 829            }
 830        }
 831        
 832        try {
 833            if (componentName == null) {
 834                String defaultComponent = 
 835                    mContext.getString(com.android.internal.R.string.default_wallpaper_component);
 836                if (defaultComponent != null) {
 837                    // See if there is a default wallpaper component specified
 838                    componentName = ComponentName.unflattenFromString(defaultComponent);
 839                    if (DEBUG) Slog.v(TAG, "Use default component wallpaper:" + componentName);
 840                }
 841                if (componentName == null) {
 842                    // Fall back to static image wallpaper
 843                    componentName = IMAGE_WALLPAPER;
 844                    //clearWallpaperComponentLocked();
 845                    //return;
 846                    if (DEBUG) Slog.v(TAG, "Using image wallpaper");
 847                }
 848            }
 849            int serviceUserId = wallpaper.userId;
 850            ServiceInfo si = mIPackageManager.getServiceInfo(componentName,
 851                    PackageManager.GET_META_DATA | PackageManager.GET_PERMISSIONS, serviceUserId);
 852            if (si == null) {
 853                // The wallpaper component we're trying to use doesn't exist
 854                Slog.w(TAG, "Attempted wallpaper " + componentName + " is unavailable");
 855                return false;
 856            }
 857            if (!android.Manifest.permission.BIND_WALLPAPER.equals(si.permission)) {
 858                String msg = "Selected service does not require "
 859                        + android.Manifest.permission.BIND_WALLPAPER
 860                        + ": " + componentName;
 861                if (fromUser) {
 862                    throw new SecurityException(msg);
 863                }
 864                Slog.w(TAG, msg);
 865                return false;
 866            }
 867            
 868            WallpaperInfo wi = null;
 869            
 870            Intent intent = new Intent(WallpaperService.SERVICE_INTERFACE);
 871            if (componentName != null && !componentName.equals(IMAGE_WALLPAPER)) {
 872                // Make sure the selected service is actually a wallpaper service.
 873                List<ResolveInfo> ris =
 874                        mIPackageManager.queryIntentServices(intent,
 875                                intent.resolveTypeIfNeeded(mContext.getContentResolver()),
 876                                PackageManager.GET_META_DATA, serviceUserId);
 877                for (int i=0; i<ris.size(); i++) {
 878                    ServiceInfo rsi = ris.get(i).serviceInfo;
 879                    if (rsi.name.equals(si.name) &&
 880                            rsi.packageName.equals(si.packageName)) {
 881                        try {
 882                            wi = new WallpaperInfo(mContext, ris.get(i));
 883                        } catch (XmlPullParserException e) {
 884                            if (fromUser) {
 885                                throw new IllegalArgumentException(e);
 886                            }
 887                            Slog.w(TAG, e);
 888                            return false;
 889                        } catch (IOException e) {
 890                            if (fromUser) {
 891                                throw new IllegalArgumentException(e);
 892                            }
 893                            Slog.w(TAG, e);
 894                            return false;
 895                        }
 896                        break;
 897                    }
 898                }
 899                if (wi == null) {
 900                    String msg = "Selected service is not a wallpaper: "
 901                            + componentName;
 902                    if (fromUser) {
 903                        throw new SecurityException(msg);
 904                    }
 905                    Slog.w(TAG, msg);
 906                    return false;
 907                }
 908            }
 909            
 910            // Bind the service!
 911            if (DEBUG) Slog.v(TAG, "Binding to:" + componentName);
 912            WallpaperConnection newConn = new WallpaperConnection(wi, wallpaper);
 913            intent.setComponent(componentName);
 914            intent.putExtra(Intent.EXTRA_CLIENT_LABEL,
 915                    com.android.internal.R.string.wallpaper_binding_label);
 916            intent.putExtra(Intent.EXTRA_CLIENT_INTENT, PendingIntent.getActivityAsUser(
 917                    mContext, 0,
 918                    Intent.createChooser(new Intent(Intent.ACTION_SET_WALLPAPER),
 919                            mContext.getText(com.android.internal.R.string.chooser_wallpaper)),
 920                    0, null, new UserHandle(serviceUserId)));
 921            if (!mContext.bindServiceAsUser(intent, newConn,
 922                    Context.BIND_AUTO_CREATE | Context.BIND_SHOWING_UI,
 923                    new UserHandle(serviceUserId))) {
 924                String msg = "Unable to bind service: "
 925                        + componentName;
 926                if (fromUser) {
 927                    throw new IllegalArgumentException(msg);
 928                }
 929                Slog.w(TAG, msg);
 930                return false;
 931            }
 932            if (wallpaper.userId == mCurrentUserId && mLastWallpaper != null) {
 933                detachWallpaperLocked(mLastWallpaper);
 934            }
 935            wallpaper.wallpaperComponent = componentName;
 936            wallpaper.connection = newConn;
 937            wallpaper.lastDiedTime = SystemClock.uptimeMillis();
 938            newConn.mReply = reply;
 939            try {
 940                if (wallpaper.userId == mCurrentUserId) {
 941                    if (DEBUG)
 942                        Slog.v(TAG, "Adding window token: " + newConn.mToken);
 943                    mIWindowManager.addWindowToken(newConn.mToken,
 944                            WindowManager.LayoutParams.TYPE_WALLPAPER);
 945                    mLastWallpaper = wallpaper;
 946                }
 947            } catch (RemoteException e) {
 948            }
 949        } catch (RemoteException e) {
 950            String msg = "Remote exception for " + componentName + "\n" + e;
 951            if (fromUser) {
 952                throw new IllegalArgumentException(msg);
 953            }
 954            Slog.w(TAG, msg);
 955            return false;
 956        }
 957        return true;
 958    }
 959
 960    void detachWallpaperLocked(WallpaperData wallpaper) {
 961        if (wallpaper.connection != null) {
 962            if (wallpaper.connection.mReply != null) {
 963                try {
 964                    wallpaper.connection.mReply.sendResult(null);
 965                } catch (RemoteException e) {
 966                }
 967                wallpaper.connection.mReply = null;
 968            }
 969            if (wallpaper.connection.mEngine != null) {
 970                try {
 971                    wallpaper.connection.mEngine.destroy();
 972                } catch (RemoteException e) {
 973                }
 974            }
 975            mContext.unbindService(wallpaper.connection);
 976            try {
 977                if (DEBUG)
 978                    Slog.v(TAG, "Removing window token: " + wallpaper.connection.mToken);
 979                mIWindowManager.removeWindowToken(wallpaper.connection.mToken);
 980            } catch (RemoteException e) {
 981            }
 982            wallpaper.connection.mService = null;
 983            wallpaper.connection.mEngine = null;
 984            wallpaper.connection = null;
 985        }
 986    }
 987
 988    void clearWallpaperComponentLocked(WallpaperData wallpaper) {
 989        wallpaper.wallpaperComponent = null;
 990        detachWallpaperLocked(wallpaper);
 991    }
 992
 993    void attachServiceLocked(WallpaperConnection conn, WallpaperData wallpaper) {
 994        try {
 995            conn.mService.attach(conn, conn.mToken,
 996                    WindowManager.LayoutParams.TYPE_WALLPAPER, false,
 997                    wallpaper.width, wallpaper.height);
 998        } catch (RemoteException e) {
 999            Slog.w(TAG, "Failed attaching wallpaper; clearing", e);
1000            if (!wallpaper.wallpaperUpdating) {
1001                bindWallpaperComponentLocked(null, false, false, wallpaper, null);
1002            }
1003        }
1004    }
1005
1006    private void notifyCallbacksLocked(WallpaperData wallpaper) {
1007        final int n = wallpaper.callbacks.beginBroadcast();
1008        for (int i = 0; i < n; i++) {
1009            try {
1010                wallpaper.callbacks.getBroadcastItem(i).onWallpaperChanged();
1011            } catch (RemoteException e) {
1012
1013                // The RemoteCallbackList will take care of removing
1014                // the dead object for us.
1015            }
1016        }
1017        wallpaper.callbacks.finishBroadcast();
1018        final Intent intent = new Intent(Intent.ACTION_WALLPAPER_CHANGED);
1019        mContext.sendBroadcastAsUser(intent, new UserHandle(mCurrentUserId));
1020    }
1021
1022    private void checkPermission(String permission) {
1023        if (PackageManager.PERMISSION_GRANTED!= mContext.checkCallingOrSelfPermission(permission)) {
1024            throw new SecurityException("Access denied to process: " + Binder.getCallingPid()
1025                    + ", must have permission " + permission);
1026        }
1027    }
1028
1029    private static JournaledFile makeJournaledFile(int userId) {
1030        final String base = new File(getWallpaperDir(userId), WALLPAPER_INFO).getAbsolutePath();
1031        return new JournaledFile(new File(base), new File(base + ".tmp"));
1032    }
1033
1034    private void saveSettingsLocked(WallpaperData wallpaper) {
1035        JournaledFile journal = makeJournaledFile(wallpaper.userId);
1036        FileOutputStream stream = null;
1037        try {
1038            stream = new FileOutputStream(journal.chooseForWrite(), false);
1039            XmlSerializer out = new FastXmlSerializer();
1040            out.setOutput(stream, "utf-8");
1041            out.startDocument(null, true);
1042
1043            out.startTag(null, "wp");
1044            out.attribute(null, "width", Integer.toString(wallpaper.width));
1045            out.attribute(null, "height", Integer.toString(wallpaper.height));
1046            out.attribute(null, "name", wallpaper.name);
1047            if (wallpaper.wallpaperComponent != null
1048                    && !wallpaper.wallpaperComponent.equals(IMAGE_WALLPAPER)) {
1049                out.attribute(null, "component",
1050                        wallpaper.wallpaperComponent.flattenToShortString());
1051            }
1052            out.endTag(null, "wp");
1053
1054            out.endDocument();
1055            stream.close();
1056            journal.commit();
1057        } catch (IOException e) {
1058            try {
1059                if (stream != null) {
1060                    stream.close();
1061                }
1062            } catch (IOException ex) {
1063                // Ignore
1064            }
1065            journal.rollback();
1066        }
1067    }
1068
1069    private void migrateFromOld() {
1070        File oldWallpaper = new File(WallpaperBackupHelper.WALLPAPER_IMAGE_KEY);
1071        File oldInfo = new File(WallpaperBackupHelper.WALLPAPER_INFO_KEY);
1072        if (oldWallpaper.exists()) {
1073            File newWallpaper = new File(getWallpaperDir(0), WALLPAPER);
1074            oldWallpaper.renameTo(newWallpaper);
1075        }
1076        if (oldInfo.exists()) {
1077            File newInfo = new File(getWallpaperDir(0), WALLPAPER_INFO);
1078            oldInfo.renameTo(newInfo);
1079        }
1080    }
1081
1082    private void loadSettingsLocked(int userId) {
1083        if (DEBUG) Slog.v(TAG, "loadSettingsLocked");
1084        
1085        JournaledFile journal = makeJournaledFile(userId);
1086        FileInputStream stream = null;
1087        File file = journal.chooseForRead();
1088        if (!file.exists()) {
1089            // This should only happen one time, when upgrading from a legacy system
1090            migrateFromOld();
1091        }
1092        WallpaperData wallpaper = mWallpaperMap.get(userId);
1093        if (wallpaper == null) {
1094            wallpaper = new WallpaperData(userId);
1095            mWallpaperMap.put(userId, wallpaper);
1096        }
1097        boolean success = false;
1098        try {
1099            stream = new FileInputStream(file);
1100            XmlPullParser parser = Xml.newPullParser();
1101            parser.setInput(stream, null);
1102
1103            int type;
1104            do {
1105                type = parser.next();
1106                if (type == XmlPullParser.START_TAG) {
1107                    String tag = parser.getName();
1108                    if ("wp".equals(tag)) {
1109                        wallpaper.width = Integer.parseInt(parser.getAttributeValue(null, "width"));
1110                        wallpaper.height = Integer.parseInt(parser
1111                                .getAttributeValue(null, "height"));
1112                        wallpaper.name = parser.getAttributeValue(null, "name");
1113                        String comp = parser.getAttributeValue(null, "component");
1114                        wallpaper.nextWallpaperComponent = comp != null
1115                                ? ComponentName.unflattenFromString(comp)
1116                                : null;
1117                        if (wallpaper.nextWallpaperComponent == null
1118                                || "android".equals(wallpaper.nextWallpaperComponent
1119                                        .getPackageName())) {
1120                            wallpaper.nextWallpaperComponent = IMAGE_WALLPAPER;
1121                        }
1122                          
1123                        if (DEBUG) {
1124                            Slog.v(TAG, "mWidth:" + wallpaper.width);
1125                            Slog.v(TAG, "mHeight:" + wallpaper.height);
1126                            Slog.v(TAG, "mName:" + wallpaper.name);
1127                            Slog.v(TAG, "mNextWallpaperComponent:"
1128                                    + wallpaper.nextWallpaperComponent);
1129                        }
1130                    }
1131                }
1132            } while (type != XmlPullParser.END_DOCUMENT);
1133            success = true;
1134        } catch (FileNotFoundException e) {
1135            Slog.w(TAG, "no current wallpaper -- first boot?");
1136        } catch (NullPointerException e) {
1137            Slog.w(TAG, "failed parsing " + file + " " + e);
1138        } catch (NumberFormatException e) {
1139            Slog.w(TAG, "failed parsing " + file + " " + e);
1140        } catch (XmlPullParserException e) {
1141            Slog.w(TAG, "failed parsing " + file + " " + e);
1142        } catch (IOException e) {
1143            Slog.w(TAG, "failed parsing " + file + " " + e);
1144        } catch (IndexOutOfBoundsException e) {
1145            Slog.w(TAG, "failed parsing " + file + " " + e);
1146        }
1147        try {
1148            if (stream != null) {
1149                stream.close();
1150            }
1151        } catch (IOException e) {
1152            // Ignore
1153        }
1154
1155        if (!success) {
1156            wallpaper.width = -1;
1157            wallpaper.height = -1;
1158            wallpaper.name = "";
1159        }
1160
1161        // We always want to have some reasonable width hint.
1162        int baseSize = getMaximumSizeDimension();
1163        if (wallpaper.width < baseSize) {
1164            wallpaper.width = baseSize;
1165        }
1166        if (wallpaper.height < baseSize) {
1167            wallpaper.height = baseSize;
1168        }
1169    }
1170
1171    private int getMaximumSizeDimension() {
1172        WindowManager wm = (WindowManager)mContext.getSystemService(Context.WINDOW_SERVICE);
1173        Display d = wm.getDefaultDisplay();
1174        return d.getMaximumSizeDimension();
1175    }
1176
1177    // Called by SystemBackupAgent after files are restored to disk.
1178    void settingsRestored() {
1179        // TODO: If necessary, make it work for secondary users as well. This currently assumes
1180        // restores only to the primary user
1181        if (DEBUG) Slog.v(TAG, "settingsRestored");
1182        WallpaperData wallpaper = null;
1183        boolean success = false;
1184        synchronized (mLock) {
1185            loadSettingsLocked(0);
1186            wallpaper = mWallpaperMap.get(0);
1187            if (wallpaper.nextWallpaperComponent != null
1188                    && !wallpaper.nextWallpaperComponent.equals(IMAGE_WALLPAPER)) {
1189                if (!bindWallpaperComponentLocked(wallpaper.nextWallpaperComponent, false, false,
1190                        wallpaper, null)) {
1191                    // No such live wallpaper or other failure; fall back to the default
1192                    // live wallpaper (since the profile being restored indicated that the
1193                    // user had selected a live rather than static one).
1194                    bindWallpaperComponentLocked(null, false, false, wallpaper, null);
1195                }
1196                success = true;
1197            } else {
1198                // If there's a wallpaper name, we use that.  If that can't be loaded, then we
1199                // use the default.
1200                if ("".equals(wallpaper.name)) {
1201                    if (DEBUG) Slog.v(TAG, "settingsRestored: name is empty");
1202                    success = true;
1203                } else {
1204                    if (DEBUG) Slog.v(TAG, "settingsRestored: attempting to restore named resource");
1205                    success = restoreNamedResourceLocked(wallpaper);
1206                }
1207                if (DEBUG) Slog.v(TAG, "settingsRestored: success=" + success);
1208                if (success) {
1209                    bindWallpaperComponentLocked(wallpaper.nextWallpaperComponent, false, false,
1210                            wallpaper, null);
1211                }
1212            }
1213        }
1214
1215        if (!success) {
1216            Slog.e(TAG, "Failed to restore wallpaper: '" + wallpaper.name + "'");
1217            wallpaper.name = "";
1218            getWallpaperDir(0).delete();
1219        }
1220
1221        synchronized (mLock) {
1222            saveSettingsLocked(wallpaper);
1223        }
1224    }
1225
1226    boolean restoreNamedResourceLocked(WallpaperData wallpaper) {
1227        if (wallpaper.name.length() > 4 && "res:".equals(wallpaper.name.substring(0, 4))) {
1228            String resName = wallpaper.name.substring(4);
1229
1230            St

Large files files are truncated, but you can click here to view the full file