/*
 * Copyright (C) 2007 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.android.server;

import android.app.ActivityManager;
import android.appwidget.AppWidgetProviderInfo;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.PackageManager;
import android.os.Binder;
import android.os.Bundle;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.IBinder;
import android.os.RemoteException;
import android.os.UserHandle;
import android.util.Slog;
import android.util.SparseArray;
import android.widget.RemoteViews;

import com.android.internal.appwidget.IAppWidgetHost;
import com.android.internal.appwidget.IAppWidgetService;
import com.android.internal.util.IndentingPrintWriter;

import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.util.List;
import java.util.Locale;


/**
 * Redirects calls to this service to the instance of the service for the appropriate user.
 */
class AppWidgetService extends IAppWidgetService.Stub
{
    private static final String TAG = "AppWidgetService";

    Context mContext;
    Locale mLocale;
    PackageManager mPackageManager;
    boolean mSafeMode;
    private final Handler mSaveStateHandler;

    private final SparseArray<AppWidgetServiceImpl> mAppWidgetServices;

    AppWidgetService(Context context) {
        mContext = context;

        HandlerThread handlerThread = new HandlerThread("AppWidgetService -- Save state");
        handlerThread.start();
        mSaveStateHandler = new Handler(handlerThread.getLooper());

        mAppWidgetServices = new SparseArray<AppWidgetServiceImpl>(5);
        AppWidgetServiceImpl primary = new AppWidgetServiceImpl(context, 0, mSaveStateHandler);
        mAppWidgetServices.append(0, primary);
    }

    public void systemReady(boolean safeMode) {
        mSafeMode = safeMode;

        mAppWidgetServices.get(0).systemReady(safeMode);

        // Register for the boot completed broadcast, so we can send the
        // ENABLE broacasts. If we try to send them now, they time out,
        // because the system isn't ready to handle them yet.
        mContext.registerReceiverAsUser(mBroadcastReceiver, UserHandle.ALL,
                new IntentFilter(Intent.ACTION_BOOT_COMPLETED), null, null);

        // Register for configuration changes so we can update the names
        // of the widgets when the locale changes.
        mContext.registerReceiverAsUser(mBroadcastReceiver, UserHandle.ALL,
                new IntentFilter(Intent.ACTION_CONFIGURATION_CHANGED), null, null);

        // Register for broadcasts about package install, etc., so we can
        // update the provider list.
        IntentFilter filter = new IntentFilter();
        filter.addAction(Intent.ACTION_PACKAGE_ADDED);
        filter.addAction(Intent.ACTION_PACKAGE_CHANGED);
        filter.addAction(Intent.ACTION_PACKAGE_REMOVED);
        filter.addDataScheme("package");
        mContext.registerReceiverAsUser(mBroadcastReceiver, UserHandle.ALL,
                filter, null, null);
        // Register for events related to sdcard installation.
        IntentFilter sdFilter = new IntentFilter();
        sdFilter.addAction(Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE);
        sdFilter.addAction(Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE);
        mContext.registerReceiverAsUser(mBroadcastReceiver, UserHandle.ALL,
                sdFilter, null, null);

        IntentFilter userFilter = new IntentFilter();
        userFilter.addAction(Intent.ACTION_USER_REMOVED);
        userFilter.addAction(Intent.ACTION_USER_STOPPING);
        mContext.registerReceiver(new BroadcastReceiver() {
            @Override
            public void onReceive(Context context, Intent intent) {
                if (Intent.ACTION_USER_REMOVED.equals(intent.getAction())) {
                    onUserRemoved(intent.getIntExtra(Intent.EXTRA_USER_HANDLE,
                            UserHandle.USER_NULL));
                } else if (Intent.ACTION_USER_STOPPING.equals(intent.getAction())) {
                    onUserStopping(intent.getIntExtra(Intent.EXTRA_USER_HANDLE,
                            UserHandle.USER_NULL));
                }
            }
        }, userFilter);
    }

    @Override
    public int allocateAppWidgetId(String packageName, int hostId, int userId)
            throws RemoteException {
        return getImplForUser(userId).allocateAppWidgetId(packageName, hostId);
    }

    @Override
    public int[] getAppWidgetIdsForHost(int hostId, int userId) throws RemoteException {
        return getImplForUser(userId).getAppWidgetIdsForHost(hostId);
    }

    @Override
    public void deleteAppWidgetId(int appWidgetId, int userId) throws RemoteException {
        getImplForUser(userId).deleteAppWidgetId(appWidgetId);
    }

    @Override
    public void deleteHost(int hostId, int userId) throws RemoteException {
        getImplForUser(userId).deleteHost(hostId);
    }

    @Override
    public void deleteAllHosts(int userId) throws RemoteException {
        getImplForUser(userId).deleteAllHosts();
    }

    @Override
    public void bindAppWidgetId(int appWidgetId, ComponentName provider, Bundle options, int userId)
            throws RemoteException {
        getImplForUser(userId).bindAppWidgetId(appWidgetId, provider, options);
    }

    @Override
    public boolean bindAppWidgetIdIfAllowed(
            String packageName, int appWidgetId, ComponentName provider, Bundle options, int userId)
                    throws RemoteException {
        return getImplForUser(userId).bindAppWidgetIdIfAllowed(
                packageName, appWidgetId, provider, options);
    }

    @Override
    public boolean hasBindAppWidgetPermission(String packageName, int userId)
            throws RemoteException {
        return getImplForUser(userId).hasBindAppWidgetPermission(packageName);
    }

    @Override
    public void setBindAppWidgetPermission(String packageName, boolean permission, int userId)
            throws RemoteException {
        getImplForUser(userId).setBindAppWidgetPermission(packageName, permission);
    }

    @Override
    public void bindRemoteViewsService(int appWidgetId, Intent intent, IBinder connection,
            int userId) throws RemoteException {
        getImplForUser(userId).bindRemoteViewsService(appWidgetId, intent, connection);
    }

    @Override
    public int[] startListening(IAppWidgetHost host, String packageName, int hostId,
            List<RemoteViews> updatedViews, int userId) throws RemoteException {
        return getImplForUser(userId).startListening(host, packageName, hostId, updatedViews);
    }

    public void onUserRemoved(int userId) {
        if (userId < 1) return;
        synchronized (mAppWidgetServices) {
            AppWidgetServiceImpl impl = mAppWidgetServices.get(userId);
            mAppWidgetServices.remove(userId);

            if (impl == null) {
                AppWidgetServiceImpl.getSettingsFile(userId).delete();
            } else {
                impl.onUserRemoved();
            }
        }
    }

    public void onUserStopping(int userId) {
        if (userId < 1) return;
        synchronized (mAppWidgetServices) {
            AppWidgetServiceImpl impl = mAppWidgetServices.get(userId);
            if (impl != null) {
                mAppWidgetServices.remove(userId);
                impl.onUserStopping();
            }
        }
    }

    private void checkPermission(int userId) {
        int realUserId = ActivityManager.handleIncomingUser(
                Binder.getCallingPid(),
                Binder.getCallingUid(),
                userId,
                false, /* allowAll */
                true, /* requireFull */
                this.getClass().getSimpleName(),
                this.getClass().getPackage().getName());
    }

    private AppWidgetServiceImpl getImplForUser(int userId) {
        checkPermission(userId);
        boolean sendInitial = false;
        AppWidgetServiceImpl service;
        synchronized (mAppWidgetServices) {
            service = mAppWidgetServices.get(userId);
            if (service == null) {
                Slog.i(TAG, "Unable to find AppWidgetServiceImpl for user " + userId + ", adding");
                // TODO: Verify that it's a valid user
                service = new AppWidgetServiceImpl(mContext, userId, mSaveStateHandler);
                service.systemReady(mSafeMode);
                // Assume that BOOT_COMPLETED was received, as this is a non-primary user.
                mAppWidgetServices.append(userId, service);
                sendInitial = true;
            }
        }
        if (sendInitial) {
            service.sendInitialBroadcasts();
        }
        return service;
    }

    @Override
    public int[] getAppWidgetIds(ComponentName provider, int userId) throws RemoteException {
        return getImplForUser(userId).getAppWidgetIds(provider);
    }

    @Override
    public AppWidgetProviderInfo getAppWidgetInfo(int appWidgetId, int userId)
            throws RemoteException {
        return getImplForUser(userId).getAppWidgetInfo(appWidgetId);
    }

    @Override
    public RemoteViews getAppWidgetViews(int appWidgetId, int userId) throws RemoteException {
        return getImplForUser(userId).getAppWidgetViews(appWidgetId);
    }

    @Override
    public void updateAppWidgetOptions(int appWidgetId, Bundle options, int userId) {
        getImplForUser(userId).updateAppWidgetOptions(appWidgetId, options);
    }

    @Override
    public Bundle getAppWidgetOptions(int appWidgetId, int userId) {
        return getImplForUser(userId).getAppWidgetOptions(appWidgetId);
    }

    @Override
    public List<AppWidgetProviderInfo> getInstalledProviders(int categoryFilter, int userId)
            throws RemoteException {
        return getImplForUser(userId).getInstalledProviders(categoryFilter);
    }

    @Override
    public void notifyAppWidgetViewDataChanged(int[] appWidgetIds, int viewId, int userId)
            throws RemoteException {
        getImplForUser(userId).notifyAppWidgetViewDataChanged(
                appWidgetIds, viewId);
    }

    @Override
    public void partiallyUpdateAppWidgetIds(int[] appWidgetIds, RemoteViews views, int userId)
            throws RemoteException {
        getImplForUser(userId).partiallyUpdateAppWidgetIds(
                appWidgetIds, views);
    }

    @Override
    public void stopListening(int hostId, int userId) throws RemoteException {
        getImplForUser(userId).stopListening(hostId);
    }

    @Override
    public void unbindRemoteViewsService(int appWidgetId, Intent intent, int userId)
            throws RemoteException {
        getImplForUser(userId).unbindRemoteViewsService(
                appWidgetId, intent);
    }

    @Override
    public void updateAppWidgetIds(int[] appWidgetIds, RemoteViews views, int userId)
            throws RemoteException {
        getImplForUser(userId).updateAppWidgetIds(appWidgetIds, views);
    }

    @Override
    public void updateAppWidgetProvider(ComponentName provider, RemoteViews views, int userId)
            throws RemoteException {
        getImplForUser(userId).updateAppWidgetProvider(provider, views);
    }

    @Override
    public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
        mContext.enforceCallingOrSelfPermission(android.Manifest.permission.DUMP, TAG);

        // Dump the state of all the app widget providers
        synchronized (mAppWidgetServices) {
            IndentingPrintWriter ipw = new IndentingPrintWriter(pw, "  ");
            for (int i = 0; i < mAppWidgetServices.size(); i++) {
                pw.println("User: " + mAppWidgetServices.keyAt(i));
                ipw.increaseIndent();
                AppWidgetServiceImpl service = mAppWidgetServices.valueAt(i);
                service.dump(fd, ipw, args);
                ipw.decreaseIndent();
            }
        }
    }

    BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
        public void onReceive(Context context, Intent intent) {
            String action = intent.getAction();
            // Slog.d(TAG, "received " + action);
            if (Intent.ACTION_BOOT_COMPLETED.equals(action)) {
                int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, UserHandle.USER_NULL);
                if (userId >= 0) {
                    getImplForUser(userId).sendInitialBroadcasts();
                } else {
                    Slog.w(TAG, "Incorrect user handle supplied in " + intent);
                }
            } else if (Intent.ACTION_CONFIGURATION_CHANGED.equals(action)) {
                for (int i = 0; i < mAppWidgetServices.size(); i++) {
                    AppWidgetServiceImpl service = mAppWidgetServices.valueAt(i);
                    service.onConfigurationChanged();
                }
            } else {
                int sendingUser = getSendingUserId();
                if (sendingUser == UserHandle.USER_ALL) {
                    for (int i = 0; i < mAppWidgetServices.size(); i++) {
                        AppWidgetServiceImpl service = mAppWidgetServices.valueAt(i);
                        service.onBroadcastReceived(intent);
                    }
                } else {
                    AppWidgetServiceImpl service = mAppWidgetServices.get(sendingUser);
                    if (service != null) {
                        service.onBroadcastReceived(intent);
                    }
                }
            }
        }
    };
}
