/*
 * 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.keyguard;

import com.android.internal.widget.LockPatternUtils;

import android.app.ActivityManagerNative;
import android.app.ActivityOptions;
import android.app.IActivityManager.WaitResult;
import android.appwidget.AppWidgetManager;
import android.appwidget.AppWidgetProviderInfo;
import android.content.ActivityNotFoundException;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.os.Bundle;
import android.os.Handler;
import android.os.RemoteException;
import android.os.SystemClock;
import android.os.UserHandle;
import android.provider.MediaStore;
import android.util.Log;
import android.view.WindowManager;

import com.android.keyguard.KeyguardHostView.OnDismissAction;

import java.util.List;

public abstract class KeyguardActivityLauncher {
    private static final String TAG = KeyguardActivityLauncher.class.getSimpleName();
    private static final boolean DEBUG = KeyguardHostView.DEBUG;
    private static final String META_DATA_KEYGUARD_LAYOUT = "com.android.keyguard.layout";
    private static final Intent SECURE_CAMERA_INTENT =
            new Intent(MediaStore.INTENT_ACTION_STILL_IMAGE_CAMERA_SECURE)
                    .addFlags(Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
    private static final Intent INSECURE_CAMERA_INTENT =
            new Intent(MediaStore.INTENT_ACTION_STILL_IMAGE_CAMERA);

    abstract Context getContext();

    abstract KeyguardSecurityCallback getCallback();

    abstract LockPatternUtils getLockPatternUtils();

    public static class CameraWidgetInfo {
        public String contextPackage;
        public int layoutId;
    }

    public CameraWidgetInfo getCameraWidgetInfo() {
        CameraWidgetInfo info = new CameraWidgetInfo();
        Intent intent = getCameraIntent();
        PackageManager packageManager = getContext().getPackageManager();
        final List<ResolveInfo> appList = packageManager.queryIntentActivitiesAsUser(
                intent, PackageManager.MATCH_DEFAULT_ONLY, getLockPatternUtils().getCurrentUser());
        if (appList.size() == 0) {
            if (DEBUG) Log.d(TAG, "getCameraWidgetInfo(): Nothing found");
            return null;
        }
        ResolveInfo resolved = packageManager.resolveActivityAsUser(intent,
                PackageManager.MATCH_DEFAULT_ONLY | PackageManager.GET_META_DATA,
                getLockPatternUtils().getCurrentUser());
        if (DEBUG) Log.d(TAG, "getCameraWidgetInfo(): resolved: " + resolved);
        if (wouldLaunchResolverActivity(resolved, appList)) {
            if (DEBUG) Log.d(TAG, "getCameraWidgetInfo(): Would launch resolver");
            return info;
        }
        if (resolved == null || resolved.activityInfo == null) {
            return null;
        }
        if (resolved.activityInfo.metaData == null || resolved.activityInfo.metaData.isEmpty()) {
            if (DEBUG) Log.d(TAG, "getCameraWidgetInfo(): no metadata found");
            return info;
        }
        int layoutId = resolved.activityInfo.metaData.getInt(META_DATA_KEYGUARD_LAYOUT);
        if (layoutId == 0) {
            if (DEBUG) Log.d(TAG, "getCameraWidgetInfo(): no layout specified");
            return info;
        }
        info.contextPackage = resolved.activityInfo.packageName;
        info.layoutId = layoutId;
        return info;
    }

    public void launchCamera(Handler worker, Runnable onSecureCameraStarted) {
        LockPatternUtils lockPatternUtils = getLockPatternUtils();

        // Workaround to avoid camera release/acquisition race when resuming face unlock
        // after showing lockscreen camera (bug 11063890).
        KeyguardUpdateMonitor.getInstance(getContext()).setAlternateUnlockEnabled(false);

        if (lockPatternUtils.isSecure()) {
            // Launch the secure version of the camera
            if (wouldLaunchResolverActivity(SECURE_CAMERA_INTENT)) {
                // TODO: Show disambiguation dialog instead.
                // For now, we'll treat this like launching any other app from secure keyguard.
                // When they do, user sees the system's ResolverActivity which lets them choose
                // which secure camera to use.
                launchActivity(SECURE_CAMERA_INTENT, false, false, null, null);
            } else {
                launchActivity(SECURE_CAMERA_INTENT, true, false, worker, onSecureCameraStarted);
            }
        } else {
            // Launch the normal camera
            launchActivity(INSECURE_CAMERA_INTENT, false, false, null, null);
        }
    }

    public void launchWidgetPicker(int appWidgetId) {
        Intent pickIntent = new Intent(AppWidgetManager.ACTION_KEYGUARD_APPWIDGET_PICK);

        pickIntent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId);
        pickIntent.putExtra(AppWidgetManager.EXTRA_CUSTOM_SORT, false);
        pickIntent.putExtra(AppWidgetManager.EXTRA_CATEGORY_FILTER,
                AppWidgetProviderInfo.WIDGET_CATEGORY_KEYGUARD);

        Bundle options = new Bundle();
        options.putInt(AppWidgetManager.OPTION_APPWIDGET_HOST_CATEGORY,
                AppWidgetProviderInfo.WIDGET_CATEGORY_KEYGUARD);
        pickIntent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_OPTIONS, options);
        pickIntent.addFlags(
                Intent.FLAG_ACTIVITY_NEW_TASK
                | Intent.FLAG_ACTIVITY_SINGLE_TOP
                | Intent.FLAG_ACTIVITY_CLEAR_TOP
                | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);

        launchActivity(pickIntent, false, false, null, null);
    }

    /**
     * Launches the said intent for the current foreground user.
     *
     * @param intent
     * @param showsWhileLocked true if the activity can be run on top of keyguard.
     *   See {@link WindowManager#FLAG_SHOW_WHEN_LOCKED}
     * @param useDefaultAnimations true if default transitions should be used, else suppressed.
     * @param worker if supplied along with onStarted, used to launch the blocking activity call.
     * @param onStarted if supplied along with worker, called after activity is started.
     */
    public void launchActivity(final Intent intent,
            boolean showsWhileLocked,
            boolean useDefaultAnimations,
            final Handler worker,
            final Runnable onStarted) {

        final Context context = getContext();
        final Bundle animation = useDefaultAnimations ? null
                : ActivityOptions.makeCustomAnimation(context, 0, 0).toBundle();
        launchActivityWithAnimation(intent, showsWhileLocked, animation, worker, onStarted);
    }

    public void launchActivityWithAnimation(final Intent intent,
            boolean showsWhileLocked,
            final Bundle animation,
            final Handler worker,
            final Runnable onStarted) {

        LockPatternUtils lockPatternUtils = getLockPatternUtils();
        intent.addFlags(
                Intent.FLAG_ACTIVITY_NEW_TASK
                | Intent.FLAG_ACTIVITY_SINGLE_TOP
                | Intent.FLAG_ACTIVITY_CLEAR_TOP);
        boolean isSecure = lockPatternUtils.isSecure();
        if (!isSecure || showsWhileLocked) {
            if (!isSecure) {
                dismissKeyguardOnNextActivity();
            }
            try {
                if (DEBUG) Log.d(TAG, String.format("Starting activity for intent %s at %s",
                        intent, SystemClock.uptimeMillis()));
                startActivityForCurrentUser(intent, animation, worker, onStarted);
            } catch (ActivityNotFoundException e) {
                Log.w(TAG, "Activity not found for intent + " + intent.getAction());
            }
        } else {
            // Create a runnable to start the activity and ask the user to enter their
            // credentials.
            KeyguardSecurityCallback callback = getCallback();
            callback.setOnDismissAction(new OnDismissAction() {
                @Override
                public boolean onDismiss() {
                    dismissKeyguardOnNextActivity();
                    startActivityForCurrentUser(intent, animation, worker, onStarted);
                    return true;
                }
            });
            callback.dismiss(false);
        }
    }

    private void dismissKeyguardOnNextActivity() {
        try {
            ActivityManagerNative.getDefault().dismissKeyguardOnNextActivity();
        } catch (RemoteException e) {
            Log.w(TAG, "can't dismiss keyguard on launch");
        }
    }

    private void startActivityForCurrentUser(final Intent intent, final Bundle options,
            Handler worker, final Runnable onStarted) {
        final UserHandle user = new UserHandle(UserHandle.USER_CURRENT);
        if (worker == null || onStarted == null) {
            getContext().startActivityAsUser(intent, options, user);
            return;
        }
        // if worker + onStarted are supplied, run blocking activity launch call in the background
        worker.post(new Runnable(){
            @Override
            public void run() {
                try {
                    WaitResult result = ActivityManagerNative.getDefault().startActivityAndWait(
                            null /*caller*/,
                            null /*caller pkg*/,
                            intent,
                            intent.resolveTypeIfNeeded(getContext().getContentResolver()),
                            null /*resultTo*/,
                            null /*resultWho*/,
                            0 /*requestCode*/,
                            Intent.FLAG_ACTIVITY_NEW_TASK,
                            null /*profileFile*/,
                            null /*profileFd*/,
                            options,
                            user.getIdentifier());
                    if (DEBUG) Log.d(TAG, String.format("waitResult[%s,%s,%s,%s] at %s",
                            result.result, result.thisTime, result.totalTime, result.who,
                            SystemClock.uptimeMillis()));
                } catch (RemoteException e) {
                    Log.w(TAG, "Error starting activity", e);
                    return;
                }
                try {
                    onStarted.run();
                } catch (Throwable t) {
                    Log.w(TAG, "Error running onStarted callback", t);
                }
            }});
    }

    private Intent getCameraIntent() {
        return getLockPatternUtils().isSecure() ? SECURE_CAMERA_INTENT : INSECURE_CAMERA_INTENT;
    }

    private boolean wouldLaunchResolverActivity(Intent intent) {
        PackageManager packageManager = getContext().getPackageManager();
        ResolveInfo resolved = packageManager.resolveActivityAsUser(intent,
                PackageManager.MATCH_DEFAULT_ONLY, getLockPatternUtils().getCurrentUser());
        List<ResolveInfo> appList = packageManager.queryIntentActivitiesAsUser(
                intent, PackageManager.MATCH_DEFAULT_ONLY, getLockPatternUtils().getCurrentUser());
        return wouldLaunchResolverActivity(resolved, appList);
    }

    private boolean wouldLaunchResolverActivity(ResolveInfo resolved, List<ResolveInfo> appList) {
        // If the list contains the above resolved activity, then it can't be
        // ResolverActivity itself.
        for (int i = 0; i < appList.size(); i++) {
            ResolveInfo tmp = appList.get(i);
            if (tmp.activityInfo.name.equals(resolved.activityInfo.name)
                    && tmp.activityInfo.packageName.equals(resolved.activityInfo.packageName)) {
                return false;
            }
        }
        return true;
    }
}
