summaryrefslogtreecommitdiff
path: root/main/java/com/google/android/setupcompat/internal/SetupCompatServiceInvoker.java
blob: 149da54c70a777f684ec3969d44b0c113f0a3eac (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
/*
 * Copyright (C) 2018 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.google.android.setupcompat.internal;

import android.annotation.SuppressLint;
import android.content.Context;
import android.os.Bundle;
import android.os.RemoteException;
import androidx.annotation.VisibleForTesting;
import com.google.android.setupcompat.ISetupCompatService;
import com.google.android.setupcompat.logging.internal.SetupMetricsLoggingConstants.MetricType;
import com.google.android.setupcompat.util.Logger;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.RejectedExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;

/**
 * This class is responsible for safely executing methods on SetupCompatService. To avoid memory
 * issues due to backed up queues, an upper bound of {@link
 * ExecutorProvider#SETUP_METRICS_LOGGER_MAX_QUEUED} is set on the logging executor service's queue
 * and {@link ExecutorProvider#SETUP_COMPAT_BINDBACK_MAX_QUEUED} on the overall executor service.
 * Once the upper bound is reached, metrics published after this event are dropped silently.
 *
 * <p>NOTE: This class is not meant to be used directly. Please use {@link
 * com.google.android.setupcompat.logging.SetupMetricsLogger} for publishing metric events.
 */
public class SetupCompatServiceInvoker {

  private static final Logger LOG = new Logger("SetupCompatServiceInvoker");

  @SuppressLint("DefaultLocale")
  public void logMetricEvent(@MetricType int metricType, Bundle args) {
    try {
      loggingExecutor.execute(() -> invokeLogMetric(metricType, args));
    } catch (RejectedExecutionException e) {
      LOG.e(String.format("Metric of type %d dropped since queue is full.", metricType), e);
    }
  }

  public void bindBack(String screenName, Bundle bundle) {
    try {
      setupCompatExecutor.execute(() -> invokeBindBack(screenName, bundle));
    } catch (RejectedExecutionException e) {
      LOG.e(String.format("Screen %s bind back fail.", screenName), e);
    }
  }

  private void invokeLogMetric(
      @MetricType int metricType, @SuppressWarnings("unused") Bundle args) {
    try {
      ISetupCompatService setupCompatService =
          SetupCompatServiceProvider.get(
              context, waitTimeInMillisForServiceConnection, TimeUnit.MILLISECONDS);
      if (setupCompatService != null) {
        setupCompatService.logMetric(metricType, args, Bundle.EMPTY);
      } else {
        LOG.w("logMetric failed since service reference is null. Are the permissions valid?");
      }
    } catch (InterruptedException | TimeoutException | RemoteException | IllegalStateException e) {
      LOG.e(String.format("Exception occurred while trying to log metric = [%s]", args), e);
    }
  }

  private void invokeBindBack(String screenName, Bundle bundle) {
    try {
      ISetupCompatService setupCompatService =
          SetupCompatServiceProvider.get(
              context, waitTimeInMillisForServiceConnection, TimeUnit.MILLISECONDS);
      if (setupCompatService != null) {
        setupCompatService.validateActivity(screenName, bundle);
      } else {
        LOG.w("BindBack failed since service reference is null. Are the permissions valid?");
      }
    } catch (InterruptedException | TimeoutException | RemoteException e) {
      LOG.e(
          String.format("Exception occurred while %s trying bind back to SetupWizard.", screenName),
          e);
    }
  }

  private SetupCompatServiceInvoker(Context context) {
    this.context = context;
    this.loggingExecutor = ExecutorProvider.setupCompatServiceInvoker.get();
    this.setupCompatExecutor = ExecutorProvider.setupCompatExecutor.get();
    this.waitTimeInMillisForServiceConnection = MAX_WAIT_TIME_FOR_CONNECTION_MS;
  }

  private final Context context;

  private final ExecutorService loggingExecutor;
  private final ExecutorService setupCompatExecutor;
  private final long waitTimeInMillisForServiceConnection;

  public static synchronized SetupCompatServiceInvoker get(Context context) {
    if (instance == null) {
      instance = new SetupCompatServiceInvoker(context.getApplicationContext());
    }

    return instance;
  }

  @VisibleForTesting
  static void setInstanceForTesting(SetupCompatServiceInvoker testInstance) {
    instance = testInstance;
  }

  // The instance is coming from Application context which alive during the application activate and
  // it's not depend on the activities life cycle, so we can avoid memory leak. However linter
  // cannot distinguish Application context or activity context, so we add @SuppressLint to avoid
  // lint error.
  @SuppressLint("StaticFieldLeak")
  private static SetupCompatServiceInvoker instance;

  private static final long MAX_WAIT_TIME_FOR_CONNECTION_MS = TimeUnit.SECONDS.toMillis(10);
}