• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2018 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.google.android.setupcompat.internal;
18 
19 import android.annotation.SuppressLint;
20 import android.content.Context;
21 import android.os.Bundle;
22 import android.os.RemoteException;
23 import androidx.annotation.VisibleForTesting;
24 import android.util.Log;
25 import com.google.android.setupcompat.ISetupCompatService;
26 import com.google.android.setupcompat.logging.internal.SetupMetricsLoggingConstants.MetricType;
27 import java.util.concurrent.ExecutorService;
28 import java.util.concurrent.RejectedExecutionException;
29 import java.util.concurrent.TimeUnit;
30 import java.util.concurrent.TimeoutException;
31 
32 /**
33  * This class is responsible for safely executing methods on SetupCompatService. To avoid memory
34  * issues due to backed up queues, an upper bound of {@link
35  * ExecutorProvider#SETUP_METRICS_LOGGER_MAX_QUEUED} is set on the logging executor service's queue
36  * and {@link ExecutorProvider#SETUP_COMPAT_BINDBACK_MAX_QUEUED} on the overall executor service.
37  * Once the upper bound is reached, metrics published after this event are dropped silently.
38  *
39  * <p>NOTE: This class is not meant to be used directly. Please use {@link
40  * com.google.android.setupcompat.logging.SetupMetricsLogger} for publishing metric events.
41  */
42 public class SetupCompatServiceInvoker {
43 
logMetricEvent(@etricType int metricType, Bundle args)44   public void logMetricEvent(@MetricType int metricType, Bundle args) {
45     try {
46       loggingExecutor.execute(() -> invokeLogMetric(metricType, args));
47     } catch (RejectedExecutionException e) {
48       Log.e(TAG, String.format("Metric of type %d dropped since queue is full.", metricType), e);
49     }
50   }
51 
bindBack(String screenName, Bundle bundle)52   public void bindBack(String screenName, Bundle bundle) {
53     try {
54       setupCompatExecutor.execute(() -> invokeBindBack(screenName, bundle));
55     } catch (RejectedExecutionException e) {
56       Log.e(TAG, String.format("Screen %s bind back fail.", screenName), e);
57     }
58   }
59 
invokeLogMetric( @etricType int metricType, @SuppressWarnings("unused") Bundle args)60   private void invokeLogMetric(
61       @MetricType int metricType, @SuppressWarnings("unused") Bundle args) {
62     try {
63       ISetupCompatService setupCompatService =
64           SetupCompatServiceProvider.get(
65               context, waitTimeInMillisForServiceConnection, TimeUnit.MILLISECONDS);
66       if (setupCompatService != null) {
67         setupCompatService.logMetric(metricType, args, Bundle.EMPTY);
68       } else {
69         Log.w(TAG, "logMetric failed since service reference is null. Are the permissions valid?");
70       }
71     } catch (InterruptedException | TimeoutException | RemoteException e) {
72       Log.e(TAG, String.format("Exception occurred while trying to log metric = [%s]", args), e);
73     }
74   }
75 
invokeBindBack(String screenName, Bundle bundle)76   private void invokeBindBack(String screenName, Bundle bundle) {
77     try {
78       ISetupCompatService setupCompatService =
79           SetupCompatServiceProvider.get(
80               context, waitTimeInMillisForServiceConnection, TimeUnit.MILLISECONDS);
81       if (setupCompatService != null) {
82         setupCompatService.validateActivity(screenName, bundle);
83       } else {
84         Log.w(TAG, "BindBack failed since service reference is null. Are the permissions valid?");
85       }
86     } catch (InterruptedException | TimeoutException | RemoteException e) {
87       Log.e(
88           TAG,
89           String.format("Exception occurred while %s trying bind back to SetupWizard.", screenName),
90           e);
91     }
92   }
93 
SetupCompatServiceInvoker(Context context)94   private SetupCompatServiceInvoker(Context context) {
95     this.context = context;
96     this.loggingExecutor = ExecutorProvider.setupCompatServiceInvoker.get();
97     this.setupCompatExecutor = ExecutorProvider.setupCompatExecutor.get();
98     this.waitTimeInMillisForServiceConnection = MAX_WAIT_TIME_FOR_CONNECTION_MS;
99   }
100 
101   private final Context context;
102 
103   private final ExecutorService loggingExecutor;
104   private final ExecutorService setupCompatExecutor;
105   private final long waitTimeInMillisForServiceConnection;
106 
get(Context context)107   public static synchronized SetupCompatServiceInvoker get(Context context) {
108     if (instance == null) {
109       instance = new SetupCompatServiceInvoker(context.getApplicationContext());
110     }
111 
112     return instance;
113   }
114 
115   @VisibleForTesting
setInstanceForTesting(SetupCompatServiceInvoker testInstance)116   static void setInstanceForTesting(SetupCompatServiceInvoker testInstance) {
117     instance = testInstance;
118   }
119 
120   // The instance is coming from Application context which alive during the application activate and
121   // it's not depend on the activities life cycle, so we can avoid memory leak. However linter
122   // cannot distinguish Application context or activity context, so we add @SuppressLint to avoid
123   // lint error.
124   @SuppressLint("StaticFieldLeak")
125   private static SetupCompatServiceInvoker instance;
126 
127   private static final long MAX_WAIT_TIME_FOR_CONNECTION_MS = TimeUnit.SECONDS.toMillis(10);
128   private static final String TAG = "SucServiceInvoker";
129 }
130