chromium/chrome/browser/tab/java/src/org/chromium/chrome/browser/tab/TabAssociatedApp.java

// Copyright 2019 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.chrome.browser.tab;

import android.text.TextUtils;

import androidx.annotation.Nullable;

import org.chromium.base.ContextUtils;
import org.chromium.chrome.browser.tab.Tab.LoadUrlResult;
import org.chromium.content_public.browser.ImeAdapter;
import org.chromium.content_public.browser.ImeEventObserver;
import org.chromium.content_public.browser.LoadUrlParams;
import org.chromium.content_public.browser.WebContents;
import org.chromium.ui.base.PageTransition;
import org.chromium.ui.base.WindowAndroid;

/** User data for a {@link Tab} managing an ID of an external application that opened it. */
public final class TabAssociatedApp extends TabWebContentsUserData implements ImeEventObserver {
    private static final Class<TabAssociatedApp> USER_DATA_KEY = TabAssociatedApp.class;

    /**
     * The external application that this Tab is associated with (null if not associated with any
     * app). Allows reusing of tabs opened from the same application.
     */
    private String mId;

    public static TabAssociatedApp from(Tab tab) {
        TabAssociatedApp app = get(tab);
        if (app == null) {
            app = tab.getUserDataHost().setUserData(USER_DATA_KEY, new TabAssociatedApp(tab));
        }
        return app;
    }

    private TabAssociatedApp(Tab tab) {
        super(tab);
        tab.addObserver(
                new EmptyTabObserver() {
                    @Override
                    public void onInitialized(Tab tab, String appId) {
                        if (appId != null) setAppId(appId);
                    }

                    @Override
                    public void onLoadUrl(
                            Tab tab, LoadUrlParams params, LoadUrlResult loadUrlResult) {
                        // Clear the app association if the user navigated to a different page from
                        // the omnibox.
                        if ((params.getTransitionType() & PageTransition.FROM_ADDRESS_BAR)
                                == PageTransition.FROM_ADDRESS_BAR) {
                            mId = null;
                        }
                    }

                    @Override
                    public void onDestroyed(Tab tab) {
                        tab.removeObserver(this);
                    }

                    @Override
                    public void onActivityAttachmentChanged(
                            Tab tab, @Nullable WindowAndroid window) {
                        // Intentionally do nothing to prevent automatic observer removal on
                        // detachment.
                    }
                });
    }

    private static TabAssociatedApp get(Tab tab) {
        return tab.getUserDataHost().getUserData(USER_DATA_KEY);
    }

    /**
     * @see #getAppId()
     */
    public static String getAppId(Tab tab) {
        TabAssociatedApp app = get(tab);
        return app != null ? app.getAppId() : null;
    }

    /**
     * @return Whether or not the tab was opened by an app other than Chrome.
     */
    public static boolean isOpenedFromExternalApp(Tab tab) {
        TabAssociatedApp app = get(tab);
        if (app == null) return false;

        String packageName = ContextUtils.getApplicationContext().getPackageName();
        return tab.getLaunchType() == TabLaunchType.FROM_EXTERNAL_APP
                && !TextUtils.equals(app.getAppId(), packageName);
    }

    @Override
    public void initWebContents(WebContents webContents) {
        ImeAdapter.fromWebContents(webContents).addEventObserver(this);
    }

    @Override
    public void cleanupWebContents(WebContents webContents) {}

    /**
     * Associates this tab with the external app with the specified id. Once a Tab is associated
     * with an app, it is reused when a new page is opened from that app (unless the user typed in
     * the location bar or in the page, in which case the tab is dissociated from any app)
     *
     * @param appId The ID of application associated with the tab.
     */
    public void setAppId(String name) {
        mId = name;
    }

    /**
     * @see #setAppId(String) for more information.
     *
     * @return The id of the application associated with that tab (null if not
     *         associated with an app).
     */
    public String getAppId() {
        return mId;
    }

    // ImeEventObserver

    @Override
    public void onImeEvent() {
        // Some text was set in the page. Don't reuse it if a tab is open from the same
        // external application, we might lose some user data.
        mId = null;
    }
}