public final class

PicasaService

extends Service
/*
 * Copyright (C) 2009 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.cooliris.picasa;

import java.io.IOException;
import java.util.concurrent.atomic.AtomicBoolean;

import android.accounts.Account;
import android.accounts.AccountManager;
import android.accounts.AuthenticatorException;
import android.accounts.OperationCanceledException;
import android.app.Service;
import android.content.ContentProviderClient;
import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
import android.content.SyncResult;
import android.os.Bundle;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.IBinder;
import android.os.Process;

public final class PicasaService extends Service {
    public static final String ACTION_SYNC = "com.cooliris.picasa.action.SYNC";
    public static final String ACTION_PERIODIC_SYNC = "com.cooliris.picasa.action.PERIODIC_SYNC";
    public static final String ACCOUNT_TYPE = "com.google";
    public static final String SERVICE_NAME = "lh2";
    public static final String FEATURE_SERVICE_NAME = "service_" + SERVICE_NAME;
    public static final String KEY_TYPE = "com.cooliris.SYNC_TYPE";
    public static final String KEY_ID = "com.cooliris.SYNC_ID";
    public static final int TYPE_USERS = 0;
    public static final int TYPE_USERS_ALBUMS = 1;
    public static final int TYPE_ALBUM_PHOTOS = 2;

    private final HandlerThread mSyncThread = new HandlerThread("PicasaSyncThread");
    private final Handler mSyncHandler;
    private static final AtomicBoolean sSyncPending = new AtomicBoolean(false);

    public static void requestSync(Context context, int type, long id) {
        Bundle extras = new Bundle();
        extras.putInt(KEY_TYPE, type);
        extras.putLong(KEY_ID, id);

        Account[] accounts = PicasaApi.getAccounts(context);
        for (Account account : accounts) {
            ContentResolver.requestSync(account, PicasaContentProvider.AUTHORITY, extras);
        }

        // context.startService(new Intent(context,
        // PicasaService.class).putExtras(extras));
    }

    public PicasaService() {
        super();
        mSyncThread.start();
        mSyncHandler = new Handler(mSyncThread.getLooper());
        mSyncHandler.post(new Runnable() {
            public void run() {
                Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
            }
        });
    }

    private static PicasaContentProvider getContentProvider(Context context) {
        ContentResolver cr = context.getContentResolver();
        ContentProviderClient client = cr.acquireContentProviderClient(PicasaContentProvider.AUTHORITY);
        return (PicasaContentProvider) client.getLocalContentProvider();
    }

    @Override
    public int onStartCommand(final Intent intent, int flags, final int startId) {
        mSyncHandler.post(new Runnable() {
            public void run() {
                performSync(PicasaService.this, null, intent.getExtras(), new SyncResult());
                stopSelf(startId);
            }
        });
        return START_NOT_STICKY;
    }

    @Override
    public IBinder onBind(Intent intent) {
        return new PicasaSyncAdapter(getApplicationContext()).getSyncAdapterBinder();
    }

    @Override
    public void onDestroy() {
        mSyncThread.quit();
    }

    public static boolean performSync(Context context, Account account, Bundle extras, SyncResult syncResult) {
        // Skip if another sync is pending.
        if (!sSyncPending.compareAndSet(false, true)) {
            return false;
        }

        // Perform the sync.
        performSyncImpl(context, account, extras, syncResult);

        // Mark sync as complete and notify all waiters.
        sSyncPending.set(false);
        synchronized (sSyncPending) {
            sSyncPending.notifyAll();
        }
        return true;
    }

    public static void waitForPerformSync() {
        synchronized (sSyncPending) {
            while (sSyncPending.get()) {
                try {
                    // Wait for the sync to complete.
                    sSyncPending.wait();
                } catch (InterruptedException e) {
                    // Stop waiting if interrupted.
                    break;
                }
            }
        }
    }

    private static void performSyncImpl(Context context, Account account, Bundle extras, SyncResult syncResult) {
        // Initialize newly added accounts to sync by default.
        String authority = PicasaContentProvider.AUTHORITY;
        if (extras.getBoolean(ContentResolver.SYNC_EXTRAS_INITIALIZE, false)) {
            if (account != null && ContentResolver.getIsSyncable(account, authority) < 0) {
                try {
                    ContentResolver.setIsSyncable(account, authority, getIsSyncable(context, account) ? 1 : 0);
                } catch (OperationCanceledException e) {
                } catch (IOException e) {
                }
            }
            return;
        }

        // Do nothing if sync is disabled for this account. TODO: is this
        // blocked in PicasaContentProvider too?
        if (account != null && ContentResolver.getIsSyncable(account, authority) < 0) {
            ++syncResult.stats.numSkippedEntries;
            return;
        }

        // Get the type of sync operation and the entity ID, if applicable.
        // Default to synchronize all.
        int type = extras.getInt(PicasaService.KEY_TYPE, PicasaService.TYPE_USERS_ALBUMS);
        long id = extras.getLong(PicasaService.KEY_ID, -1);

        // Get the content provider instance and reload the list of user
        // accounts.
        PicasaContentProvider provider = getContentProvider(context);
        provider.reloadAccounts();

        // Restrict sync to either a specific account or all accounts.
        provider.setActiveSyncAccount(account);

        // Perform the desired sync operation.
        switch (type) {
        case PicasaService.TYPE_USERS:
            provider.syncUsers(syncResult);
            break;
        case PicasaService.TYPE_USERS_ALBUMS:
            provider.syncUsersAndAlbums(true, syncResult);
            break;
        case PicasaService.TYPE_ALBUM_PHOTOS:
            provider.syncAlbumPhotos(id, true, syncResult);
            break;
        default:
            throw new IllegalArgumentException();
        }
    }

    private static boolean getIsSyncable(Context context, Account account) throws IOException, OperationCanceledException {
        try {
            Account[] picasaAccounts = AccountManager.get(context).getAccountsByTypeAndFeatures(ACCOUNT_TYPE,
                    new String[] { FEATURE_SERVICE_NAME }, null /* callback */, null /* handler */).getResult();
            for (Account picasaAccount : picasaAccounts) {
                if (account.equals(picasaAccount)) {
                    return true;
                }
            }
            return false;
        } catch (AuthenticatorException e) {
            throw new IOException(e.getMessage());
        }
    }
}