1 // Copyright 2019 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 package org.chromium.chrome.browser.customtabs;
6 
7 import androidx.annotation.IntDef;
8 import androidx.browser.customtabs.CustomTabsSessionToken;
9 
10 import org.chromium.base.metrics.RecordHistogram;
11 import org.chromium.chrome.browser.browserservices.BrowserServicesIntentDataProvider;
12 import org.chromium.chrome.browser.customtabs.content.CustomTabActivityTabProvider;
13 import org.chromium.chrome.browser.dependency_injection.ActivityScope;
14 import org.chromium.chrome.browser.gsa.GSAState;
15 import org.chromium.chrome.browser.lifecycle.ActivityLifecycleDispatcher;
16 import org.chromium.chrome.browser.lifecycle.StartStopWithNativeObserver;
17 import org.chromium.chrome.browser.tab.Tab;
18 import org.chromium.chrome.browser.tab.TabAssociatedApp;
19 
20 import java.lang.annotation.Retention;
21 import java.lang.annotation.RetentionPolicy;
22 
23 import javax.inject.Inject;
24 
25 /**
26  * Keeps the client app alive, when possible, while CustomTabActivity is in foreground (see
27  * {@link CustomTabsConnection#keepAliveForSession}).
28  */
29 @ActivityScope
30 public class CustomTabActivityClientConnectionKeeper implements StartStopWithNativeObserver {
31 
32     @IntDef({ConnectionStatus.DISCONNECTED, ConnectionStatus.DISCONNECTED_KEEP_ALIVE,
33             ConnectionStatus.CONNECTED, ConnectionStatus.CONNECTED_KEEP_ALIVE})
34     @Retention(RetentionPolicy.SOURCE)
35     private @interface ConnectionStatus {
36         int DISCONNECTED = 0;
37         int DISCONNECTED_KEEP_ALIVE = 1;
38         int CONNECTED = 2;
39         int CONNECTED_KEEP_ALIVE = 3;
40         int NUM_ENTRIES = 4;
41     }
42 
43     private final CustomTabsConnection mConnection;
44     private final BrowserServicesIntentDataProvider mIntentDataProvider;
45     private final CustomTabActivityTabProvider mTabProvider;
46 
47     private boolean mIsKeepingAlive;
48 
49     @Inject
CustomTabActivityClientConnectionKeeper(CustomTabsConnection connection, BrowserServicesIntentDataProvider intentDataProvider, ActivityLifecycleDispatcher lifecycleDispatcher, CustomTabActivityTabProvider tabProvider)50     public CustomTabActivityClientConnectionKeeper(CustomTabsConnection connection,
51             BrowserServicesIntentDataProvider intentDataProvider,
52             ActivityLifecycleDispatcher lifecycleDispatcher,
53             CustomTabActivityTabProvider tabProvider) {
54         mConnection = connection;
55         mIntentDataProvider = intentDataProvider;
56         mTabProvider = tabProvider;
57         lifecycleDispatcher.register(this);
58     }
59 
60     @Override
onStartWithNative()61     public void onStartWithNative() {
62         mIsKeepingAlive = mConnection.keepAliveForSession(
63                 mIntentDataProvider.getSession(), mIntentDataProvider.getKeepAliveServiceIntent());
64     }
65 
66     @Override
onStopWithNative()67     public void onStopWithNative() {
68         mConnection.dontKeepAliveForSession(mIntentDataProvider.getSession());
69         mIsKeepingAlive = false;
70     }
71 
72     /**
73      * Records current client connection status.
74      */
recordClientConnectionStatus()75     public void recordClientConnectionStatus() {
76         Tab tab = mTabProvider.getTab();
77         String packageName = tab == null ? null : TabAssociatedApp.getAppId(tab);
78         if (packageName == null) return; // No associated package
79 
80         CustomTabsSessionToken session = mIntentDataProvider.getSession();
81         boolean isConnected =
82                 packageName.equals(mConnection.getClientPackageNameForSession(session));
83         int status = -1;
84         if (isConnected) {
85             if (mIsKeepingAlive) {
86                 status = ConnectionStatus.CONNECTED_KEEP_ALIVE;
87             } else {
88                 status = ConnectionStatus.CONNECTED;
89             }
90         } else {
91             if (mIsKeepingAlive) {
92                 status = ConnectionStatus.DISCONNECTED_KEEP_ALIVE;
93             } else {
94                 status = ConnectionStatus.DISCONNECTED;
95             }
96         }
97         assert status >= 0;
98 
99         if (GSAState.isGsaPackageName(packageName)) {
100             RecordHistogram.recordEnumeratedHistogram("CustomTabs.ConnectionStatusOnReturn.GSA",
101                     status, ConnectionStatus.NUM_ENTRIES);
102         } else {
103             RecordHistogram.recordEnumeratedHistogram("CustomTabs.ConnectionStatusOnReturn.NonGSA",
104                     status, ConnectionStatus.NUM_ENTRIES);
105         }
106     }
107 }
108