chromium/ui/android/java/src/org/chromium/ui/base/IntentRequestTrackerImpl.java

// Copyright 2021 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

package org.chromium.ui.base;

import static org.chromium.ui.base.WindowAndroid.START_INTENT_FAILURE;

import android.app.Activity;
import android.app.PendingIntent;
import android.content.Intent;
import android.os.Bundle;
import android.util.SparseArray;

import org.chromium.base.Callback;
import org.chromium.base.ContextUtils;
import org.chromium.ui.base.WindowAndroid.IntentCallback;

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

/** The implementation of IntentRequestTracker. */
/* package */ final class IntentRequestTrackerImpl implements IntentRequestTracker {
    // Constants used for intent request code bounding.
    private static final int REQUEST_CODE_PREFIX = 1000;
    private static final int REQUEST_CODE_RANGE_SIZE = 100;

    private final SparseArray<IntentCallback> mOutstandingIntents;
    private int mNextRequestCode;
    private final Delegate mDelegate;

    // Ideally, this would be a SparseArray<String>, but there's no easy way to store a
    // SparseArray<String> in a bundle during saveInstanceState(). So we use a HashMap and suppress
    // the Android lint warning "UseSparseArrays".
    private HashMap<Integer, String> mIntentErrors;

    /**
     * Creates an instance of the class.
     * @param delegate The delegate that wraps the activity that owns the tracker.
     */
    /* package */ IntentRequestTrackerImpl(Delegate delegate) {
        mOutstandingIntents = new SparseArray<>();
        mIntentErrors = new HashMap<>();
        mDelegate = delegate;
    }

    /* package */ int showCancelableIntent(
            PendingIntent intent, IntentCallback callback, Integer errorId) {
        int requestCode = generateNextRequestCode();

        if (!mDelegate.startIntentSenderForResult(intent.getIntentSender(), requestCode)) {
            return START_INTENT_FAILURE;
        }

        storeCallbackData(requestCode, callback, errorId);
        return requestCode;
    }

    /* package */ int showCancelableIntent(
            Intent intent, IntentCallback callback, Integer errorId) {
        int requestCode = generateNextRequestCode();

        if (!mDelegate.startActivityForResult(intent, requestCode)) {
            return START_INTENT_FAILURE;
        }

        storeCallbackData(requestCode, callback, errorId);
        return requestCode;
    }

    /* package */ int showCancelableIntent(
            Callback<Integer> intentTrigger, IntentCallback callback, Integer errorId) {
        int requestCode = generateNextRequestCode();

        intentTrigger.onResult(requestCode);

        storeCallbackData(requestCode, callback, errorId);
        return requestCode;
    }

    /* package */ void cancelIntent(int requestCode) {
        mDelegate.finishActivity(requestCode);
    }

    /* package */ boolean removeIntentCallback(IntentCallback callback) {
        int requestCode = getOutstandingIntents().indexOfValue(callback);
        if (requestCode < 0) return false;
        getOutstandingIntents().remove(requestCode);
        mIntentErrors.remove(requestCode);
        return true;
    }

    @Override
    public boolean onActivityResult(int requestCode, int resultCode, Intent data) {
        IntentCallback callback = getOutstandingIntents().get(requestCode);
        getOutstandingIntents().delete(requestCode);
        String errorMessage = mIntentErrors.remove(requestCode);

        if (callback != null) {
            callback.onIntentCompleted(resultCode, data);
            return true;
        } else {
            if (errorMessage != null && !mDelegate.onCallbackNotFoundError(errorMessage)) {
                WindowAndroid.showError(errorMessage);
            }
        }
        return false;
    }

    @Override
    public WeakReference<Activity> getActivity() {
        return mDelegate.getActivity();
    }

    @Override
    public void saveInstanceState(Bundle bundle) {
        bundle.putSerializable(WindowAndroid.WINDOW_CALLBACK_ERRORS, mIntentErrors);
    }

    @Override
    public void restoreInstanceState(Bundle bundle) {
        if (bundle == null) return;

        Object errors = bundle.getSerializable(WindowAndroid.WINDOW_CALLBACK_ERRORS);
        if (errors instanceof HashMap) {
            @SuppressWarnings("unchecked")
            HashMap<Integer, String> intentErrors = (HashMap<Integer, String>) errors;
            mIntentErrors = intentErrors;
        }
    }

    private int generateNextRequestCode() {
        int requestCode = REQUEST_CODE_PREFIX + mNextRequestCode;
        mNextRequestCode = (mNextRequestCode + 1) % REQUEST_CODE_RANGE_SIZE;
        return requestCode;
    }

    private void storeCallbackData(int requestCode, IntentCallback callback, Integer errorId) {
        mOutstandingIntents.put(requestCode, callback);
        mIntentErrors.put(
                requestCode,
                errorId == null ? null : ContextUtils.getApplicationContext().getString(errorId));
    }

    private SparseArray<IntentCallback> getOutstandingIntents() {
        return mOutstandingIntents;
    }
}