/*
 * Copyright (C) 2012 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.settings;

import android.app.Activity;
import android.app.ActivityManager;
import android.app.LauncherActivity.IconResizer;
import android.appwidget.AppWidgetHost;
import android.appwidget.AppWidgetManager;
import android.appwidget.AppWidgetProviderInfo;
import android.content.ActivityNotFoundException;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.Bitmap.Config;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.os.AsyncTask;
import android.os.Bundle;
import android.os.IBinder;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.UserHandle;
import android.util.DisplayMetrics;
import android.util.Log;
import android.view.IWindowManager;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AdapterView;
import android.widget.BaseAdapter;
import android.widget.GridView;
import android.widget.ImageView;
import android.widget.TextView;
import android.widget.Toast;

import com.android.internal.widget.LockPatternUtils;

import java.lang.ref.WeakReference;
import java.util.List;

/**
 * Displays a list of {@link AppWidgetProviderInfo} widgets, along with any
 * injected special widgets specified through
 * {@link AppWidgetManager#EXTRA_CUSTOM_INFO} and
 * {@link AppWidgetManager#EXTRA_CUSTOM_EXTRAS}.
 * <p>
 * When an installed {@link AppWidgetProviderInfo} is selected, this activity
 * will bind it to the given {@link AppWidgetManager#EXTRA_APPWIDGET_ID},
 * otherwise it will return the requested extras.
 */
public class KeyguardAppWidgetPickActivity extends Activity
    implements GridView.OnItemClickListener,
        AppWidgetLoader.ItemConstructor<KeyguardAppWidgetPickActivity.Item> {
    private static final String TAG = "KeyguardAppWidgetPickActivity";
    private static final int REQUEST_PICK_APPWIDGET = 126;
    private static final int REQUEST_CREATE_APPWIDGET = 127;

    private AppWidgetLoader<Item> mAppWidgetLoader;
    private List<Item> mItems;
    private GridView mGridView;
    private AppWidgetAdapter mAppWidgetAdapter;
    private AppWidgetManager mAppWidgetManager;
    private int mAppWidgetId;
    // Might make it possible to make this be false in future
    private boolean mAddingToKeyguard = true;
    private Intent mResultData;
    private LockPatternUtils mLockPatternUtils;
    private Bundle mExtraConfigureOptions;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        setContentView(R.layout.keyguard_appwidget_picker_layout);
        super.onCreate(savedInstanceState);

        // Set default return data
        setResultData(RESULT_CANCELED, null);

        // Read the appWidgetId passed our direction, otherwise bail if not found
        final Intent intent = getIntent();
        if (intent.hasExtra(AppWidgetManager.EXTRA_APPWIDGET_ID)) {
            mAppWidgetId = intent.getIntExtra(AppWidgetManager.EXTRA_APPWIDGET_ID,
                    AppWidgetManager.INVALID_APPWIDGET_ID);
        } else {
            finish();
        }
        mExtraConfigureOptions = intent.getBundleExtra(AppWidgetManager.EXTRA_APPWIDGET_OPTIONS);

        mGridView = (GridView) findViewById(R.id.widget_list);
        DisplayMetrics dm = new DisplayMetrics();
        getWindowManager().getDefaultDisplay().getMetrics(dm);
        int maxGridWidth = getResources().getDimensionPixelSize(
                R.dimen.keyguard_appwidget_picker_max_width);

        if (maxGridWidth < dm.widthPixels) {
            mGridView.getLayoutParams().width = maxGridWidth;
        }
        mAppWidgetManager = AppWidgetManager.getInstance(this);
        mAppWidgetLoader = new AppWidgetLoader<Item>(this, mAppWidgetManager, this);
        mItems = mAppWidgetLoader.getItems(getIntent());
        mAppWidgetAdapter = new AppWidgetAdapter(this, mItems);
        mGridView.setAdapter(mAppWidgetAdapter);
        mGridView.setOnItemClickListener(this);

        mLockPatternUtils = new LockPatternUtils(this); // TEMP-- we want to delete this
    }

    /**
     * Convenience method for setting the result code and intent. This method
     * correctly injects the {@link AppWidgetManager#EXTRA_APPWIDGET_ID} that
     * most hosts expect returned.
     */
    void setResultData(int code, Intent intent) {
        Intent result = intent != null ? intent : new Intent();
        result.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, mAppWidgetId);
        mResultData = result;
        setResult(code, result);
    }

    /**
     * Item that appears in the AppWidget picker grid.
     */
    public static class Item implements AppWidgetLoader.LabelledItem {
        protected static IconResizer sResizer;


        CharSequence label;
        int appWidgetPreviewId;
        int iconId;
        String packageName;
        String className;
        Bundle extras;
        private WidgetPreviewLoader mWidgetPreviewLoader;
        private Context mContext;

        /**
         * Create a list item from given label and icon.
         */
        Item(Context context, CharSequence label) {
            this.label = label;
            mContext = context;
        }

        void loadWidgetPreview(ImageView v) {
            mWidgetPreviewLoader = new WidgetPreviewLoader(mContext, v);
            mWidgetPreviewLoader.executeOnExecutor(AsyncTask.SERIAL_EXECUTOR, (Void[]) null);
        }

        void cancelLoadingWidgetPreview() {
            if (mWidgetPreviewLoader != null) {
                mWidgetPreviewLoader.cancel(false);
                mWidgetPreviewLoader = null;
            }
        }

        /**
         * Build the {@link Intent} described by this item. If this item
         * can't create a valid {@link android.content.ComponentName}, it will return
         * {@link Intent#ACTION_CREATE_SHORTCUT} filled with the item label.
         */
        Intent getIntent() {
            Intent intent = new Intent();
            if (packageName != null && className != null) {
                // Valid package and class, so fill details as normal intent
                intent.setClassName(packageName, className);
                if (extras != null) {
                    intent.putExtras(extras);
                }
            } else {
                // No valid package or class, so treat as shortcut with label
                intent.setAction(Intent.ACTION_CREATE_SHORTCUT);
                intent.putExtra(Intent.EXTRA_SHORTCUT_NAME, label);
            }
            return intent;
        }

        public CharSequence getLabel() {
            return label;
        }

        class WidgetPreviewLoader extends AsyncTask<Void, Bitmap, Void> {
            private Resources mResources;
            private PackageManager mPackageManager;
            private int mIconDpi;
            private ImageView mView;
            public WidgetPreviewLoader(Context context, ImageView v) {
                super();
                mResources = context.getResources();
                mPackageManager = context.getPackageManager();
                ActivityManager activityManager =
                        (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
                mIconDpi = activityManager.getLauncherLargeIconDensity();
                mView = v;
            }
            public Void doInBackground(Void... params) {
                if (!isCancelled()) {
                    int appWidgetPreviewWidth =
                            mResources.getDimensionPixelSize(R.dimen.appwidget_preview_width);
                    int appWidgetPreviewHeight =
                            mResources.getDimensionPixelSize(R.dimen.appwidget_preview_height);
                    Bitmap b = getWidgetPreview(new ComponentName(packageName, className),
                            appWidgetPreviewId, iconId,
                            appWidgetPreviewWidth, appWidgetPreviewHeight);
                    publishProgress(b);
                }
                return null;
            }
            public void onProgressUpdate(Bitmap... values) {
                if (!isCancelled()) {
                    Bitmap b = values[0];
                    mView.setImageBitmap(b);
                }
            }
            abstract class WeakReferenceThreadLocal<T> {
                private ThreadLocal<WeakReference<T>> mThreadLocal;
                public WeakReferenceThreadLocal() {
                    mThreadLocal = new ThreadLocal<WeakReference<T>>();
                }

                abstract T initialValue();

                public void set(T t) {
                    mThreadLocal.set(new WeakReference<T>(t));
                }

                public T get() {
                    WeakReference<T> reference = mThreadLocal.get();
                    T obj;
                    if (reference == null) {
                        obj = initialValue();
                        mThreadLocal.set(new WeakReference<T>(obj));
                        return obj;
                    } else {
                        obj = reference.get();
                        if (obj == null) {
                            obj = initialValue();
                            mThreadLocal.set(new WeakReference<T>(obj));
                        }
                        return obj;
                    }
                }
            }

            class CanvasCache extends WeakReferenceThreadLocal<Canvas> {
                @Override
                protected Canvas initialValue() {
                    return new Canvas();
                }
            }

            class PaintCache extends WeakReferenceThreadLocal<Paint> {
                @Override
                protected Paint initialValue() {
                    return null;
                }
            }

            class BitmapCache extends WeakReferenceThreadLocal<Bitmap> {
                @Override
                protected Bitmap initialValue() {
                    return null;
                }
            }

            class RectCache extends WeakReferenceThreadLocal<Rect> {
                @Override
                protected Rect initialValue() {
                    return new Rect();
                }
            }

            // Used for drawing widget previews
            CanvasCache sCachedAppWidgetPreviewCanvas = new CanvasCache();
            RectCache sCachedAppWidgetPreviewSrcRect = new RectCache();
            RectCache sCachedAppWidgetPreviewDestRect = new RectCache();
            PaintCache sCachedAppWidgetPreviewPaint = new PaintCache();

            private Bitmap getWidgetPreview(ComponentName provider, int previewImage,
                    int iconId, int maxWidth, int maxHeight) {
                // Load the preview image if possible
                String packageName = provider.getPackageName();
                if (maxWidth < 0) maxWidth = Integer.MAX_VALUE;
                if (maxHeight < 0) maxHeight = Integer.MAX_VALUE;


                int appIconSize = mResources.getDimensionPixelSize(R.dimen.app_icon_size);

                Drawable drawable = null;
                if (previewImage != 0) {
                    drawable = mPackageManager.getDrawable(packageName, previewImage, null);
                    if (drawable == null) {
                        Log.w(TAG, "Can't load widget preview drawable 0x" +
                                Integer.toHexString(previewImage) + " for provider: " + provider);
                    }
                }

                int bitmapWidth;
                int bitmapHeight;
                Bitmap defaultPreview = null;
                boolean widgetPreviewExists = (drawable != null);
                if (widgetPreviewExists) {
                    bitmapWidth = drawable.getIntrinsicWidth();
                    bitmapHeight = drawable.getIntrinsicHeight();
                } else {
                    // Generate a preview image if we couldn't load one
                    bitmapWidth = appIconSize;
                    bitmapHeight = appIconSize;
                    defaultPreview = Bitmap.createBitmap(bitmapWidth, bitmapHeight,
                            Config.ARGB_8888);

                    try {
                        Drawable icon = null;
                        if (iconId > 0)
                            icon = getFullResIcon(packageName, iconId);
                        if (icon != null) {
                            renderDrawableToBitmap(icon, defaultPreview, 0,
                                    0, appIconSize, appIconSize);
                        }
                    } catch (Resources.NotFoundException e) {
                    }
                }

                // Scale to fit width only - let the widget preview be clipped in the
                // vertical dimension
                float scale = 1f;
                if (bitmapWidth > maxWidth) {
                    scale = maxWidth / (float) bitmapWidth;
                }
                int finalPreviewWidth = (int) (scale * bitmapWidth);
                int finalPreviewHeight = (int) (scale * bitmapHeight);

                bitmapWidth = finalPreviewWidth;
                bitmapHeight = Math.min(finalPreviewHeight, maxHeight);

                Bitmap preview = Bitmap.createBitmap(bitmapWidth, bitmapHeight,
                        Config.ARGB_8888);

                // Draw the scaled preview into the final bitmap
                if (widgetPreviewExists) {
                    renderDrawableToBitmap(drawable, preview, 0, 0, finalPreviewWidth,
                            finalPreviewHeight);
                } else {
                    final Canvas c = sCachedAppWidgetPreviewCanvas.get();
                    final Rect src = sCachedAppWidgetPreviewSrcRect.get();
                    final Rect dest = sCachedAppWidgetPreviewDestRect.get();
                    c.setBitmap(preview);
                    src.set(0, 0, defaultPreview.getWidth(), defaultPreview.getHeight());
                    dest.set(0, 0, finalPreviewWidth, finalPreviewHeight);

                    Paint p = sCachedAppWidgetPreviewPaint.get();
                    if (p == null) {
                        p = new Paint();
                        p.setFilterBitmap(true);
                        sCachedAppWidgetPreviewPaint.set(p);
                    }
                    c.drawBitmap(defaultPreview, src, dest, p);
                    c.setBitmap(null);
                }
                return preview;
            }
            public Drawable getFullResDefaultActivityIcon() {
                return getFullResIcon(Resources.getSystem(),
                        android.R.mipmap.sym_def_app_icon);
            }

            public Drawable getFullResIcon(Resources resources, int iconId) {
                Drawable d;
                try {
                    d = resources.getDrawableForDensity(iconId, mIconDpi);
                } catch (Resources.NotFoundException e) {
                    d = null;
                }

                return (d != null) ? d : getFullResDefaultActivityIcon();
            }

            public Drawable getFullResIcon(String packageName, int iconId) {
                Resources resources;
                try {
                    resources = mPackageManager.getResourcesForApplication(packageName);
                } catch (PackageManager.NameNotFoundException e) {
                    resources = null;
                }
                if (resources != null) {
                    if (iconId != 0) {
                        return getFullResIcon(resources, iconId);
                    }
                }
                return getFullResDefaultActivityIcon();
            }

            private void renderDrawableToBitmap(Drawable d, Bitmap bitmap, int x, int y, int w, int h) {
                renderDrawableToBitmap(d, bitmap, x, y, w, h, 1f);
            }

            private void renderDrawableToBitmap(Drawable d, Bitmap bitmap, int x, int y, int w, int h,
                    float scale) {
                if (bitmap != null) {
                    Canvas c = new Canvas(bitmap);
                    c.scale(scale, scale);
                    Rect oldBounds = d.copyBounds();
                    d.setBounds(x, y, x + w, y + h);
                    d.draw(c);
                    d.setBounds(oldBounds); // Restore the bounds
                    c.setBitmap(null);
                }
            }
        }
    }

    @Override
    public Item createItem(Context context, AppWidgetProviderInfo info, Bundle extras) {
        CharSequence label = info.label;

        Item item = new Item(context, label);
        item.appWidgetPreviewId = info.previewImage;
        item.iconId = info.icon;
        item.packageName = info.provider.getPackageName();
        item.className = info.provider.getClassName();
        item.extras = extras;
        return item;
    }

    protected static class AppWidgetAdapter extends BaseAdapter {
        private final LayoutInflater mInflater;
        private final List<Item> mItems;

        /**
         * Create an adapter for the given items.
         */
        public AppWidgetAdapter(Context context, List<Item> items) {
            mInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
            mItems = items;
        }

        /**
         * {@inheritDoc}
         */
        public int getCount() {
            return mItems.size();
        }

        /**
         * {@inheritDoc}
         */
        public Object getItem(int position) {
            return mItems.get(position);
        }

        /**
         * {@inheritDoc}
         */
        public long getItemId(int position) {
            return position;
        }

        /**
         * {@inheritDoc}
         */
        public View getView(int position, View convertView, ViewGroup parent) {
            if (convertView == null) {
                convertView = mInflater.inflate(R.layout.keyguard_appwidget_item, parent, false);
            }

            Item item = (Item) getItem(position);
            TextView textView = (TextView) convertView.findViewById(R.id.label);
            textView.setText(item.label);
            ImageView iconView = (ImageView) convertView.findViewById(R.id.icon);
            iconView.setImageDrawable(null);
            item.loadWidgetPreview(iconView);
            return convertView;
        }

        public void cancelAllWidgetPreviewLoaders() {
            for (int i = 0; i < mItems.size(); i++) {
                mItems.get(i).cancelLoadingWidgetPreview();
            }
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
        Item item = mItems.get(position);
        Intent intent = item.getIntent();

        int result;
        if (item.extras != null) {
            // If these extras are present it's because this entry is custom.
            // Don't try to bind it, just pass it back to the app.
            result = RESULT_OK;
            setResultData(result, intent);
        } else {
            try {
                if (mAddingToKeyguard && mAppWidgetId == AppWidgetManager.INVALID_APPWIDGET_ID) {
                    // Found in KeyguardHostView.java
                    final int KEYGUARD_HOST_ID = 0x4B455947;
                    int userId = ActivityManager.getCurrentUser();
                    mAppWidgetId = AppWidgetHost.allocateAppWidgetIdForSystem(KEYGUARD_HOST_ID,
                            userId);
                }
                mAppWidgetManager.bindAppWidgetId(
                        mAppWidgetId, intent.getComponent(), mExtraConfigureOptions);
                result = RESULT_OK;
            } catch (IllegalArgumentException e) {
                // This is thrown if they're already bound, or otherwise somehow
                // bogus.  Set the result to canceled, and exit.  The app *should*
                // clean up at this point.  We could pass the error along, but
                // it's not clear that that's useful -- the widget will simply not
                // appear.
                result = RESULT_CANCELED;
            }
            setResultData(result, null);
        }
        if (mAddingToKeyguard) {
            onActivityResult(REQUEST_PICK_APPWIDGET, result, mResultData);
        } else {
            finish();
        }
    }

    protected void onDestroy() {
        if (mAppWidgetAdapter != null) {
            mAppWidgetAdapter.cancelAllWidgetPreviewLoaders();
        }
        super.onDestroy();
    }

    @Override
    public void onActivityResult(int requestCode, int resultCode, Intent data) {
        super.onActivityResult(requestCode, resultCode, data);
        if (requestCode == REQUEST_PICK_APPWIDGET || requestCode == REQUEST_CREATE_APPWIDGET) {
            int appWidgetId;
            if  (data == null) {
                appWidgetId = AppWidgetManager.INVALID_APPWIDGET_ID ;
            } else {
                appWidgetId = data.getIntExtra(
                        AppWidgetManager.EXTRA_APPWIDGET_ID, AppWidgetManager.INVALID_APPWIDGET_ID);
            }
            if (requestCode == REQUEST_PICK_APPWIDGET && resultCode == Activity.RESULT_OK) {
                AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(this);

                AppWidgetProviderInfo appWidget = null;
                appWidget = appWidgetManager.getAppWidgetInfo(appWidgetId);

                if (appWidget.configure != null) {
                    // Launch over to configure widget, if needed
                    Intent intent = new Intent(AppWidgetManager.ACTION_APPWIDGET_CONFIGURE);
                    intent.setComponent(appWidget.configure);
                    intent.addFlags(Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
                    intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId);

                    startActivityForResultSafely(intent, REQUEST_CREATE_APPWIDGET);
                } else {
                    // Otherwise just add it
                    onActivityResult(REQUEST_CREATE_APPWIDGET, Activity.RESULT_OK, data);
                }
            } else if (requestCode == REQUEST_CREATE_APPWIDGET && resultCode == Activity.RESULT_OK) {
                mLockPatternUtils.addAppWidget(appWidgetId, 0);
                finishDelayedAndShowLockScreen(appWidgetId);
            } else {
                if (mAddingToKeyguard &&
                        mAppWidgetId != AppWidgetManager.INVALID_APPWIDGET_ID) {
                    int userId = ActivityManager.getCurrentUser();
                    AppWidgetHost.deleteAppWidgetIdForSystem(mAppWidgetId, userId);
                }
                finishDelayedAndShowLockScreen(AppWidgetManager.INVALID_APPWIDGET_ID);
            }
        }
    }

    private void finishDelayedAndShowLockScreen(int appWidgetId) {
        IBinder b = ServiceManager.getService(Context.WINDOW_SERVICE);
        IWindowManager iWm = IWindowManager.Stub.asInterface(b);
        Bundle opts = null;
        if (appWidgetId != AppWidgetManager.INVALID_APPWIDGET_ID) {
            opts = new Bundle();
            opts.putInt(LockPatternUtils.KEYGUARD_SHOW_APPWIDGET, appWidgetId);
        }
        try {
            iWm.lockNow(opts);
        } catch (RemoteException e) {
        }

        // Change background to all black
        ViewGroup root = (ViewGroup) findViewById(R.id.layout_root);
        root.setBackgroundColor(0xFF000000);
        // Hide all children
        final int childCount = root.getChildCount();
        for (int i = 0; i < childCount; i++) {
            root.getChildAt(i).setVisibility(View.INVISIBLE);
        }
        mGridView.postDelayed(new Runnable() {
            public void run() {
                finish();
            }
        }, 500);
    }

    void startActivityForResultSafely(Intent intent, int requestCode) {
        try {
            startActivityForResult(intent, requestCode);
        } catch (ActivityNotFoundException e) {
            Toast.makeText(this, R.string.activity_not_found, Toast.LENGTH_SHORT).show();
        } catch (SecurityException e) {
            Toast.makeText(this, R.string.activity_not_found, Toast.LENGTH_SHORT).show();
            Log.e(TAG, "Settings does not have the permission to launch " + intent, e);
        }
    }
}
