• 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 com.google.android.setupcompat.ISetupCompatService;
25 import com.google.android.setupcompat.logging.internal.SetupMetricsLoggingConstants.MetricType;
26 import com.google.android.setupcompat.util.Logger;
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 
44   private static final Logger LOG = new Logger("SetupCompatServiceInvoker");
45 
46   @SuppressLint("DefaultLocale")
logMetricEvent(@etricType int metricType, Bundle args)47   public void logMetricEvent(@MetricType int metricType, Bundle args) {
48     try {
49       loggingExecutor.execute(() -> invokeLogMetric(metricType, args));
50     } catch (RejectedExecutionException e) {
51       LOG.e(String.format("Metric of type %d dropped since queue is full.", metricType), e);
52     }
53   }
54 
bindBack(String screenName, Bundle bundle)55   public void bindBack(String screenName, Bundle bundle) {
56     try {
57       loggingExecutor.execute(() -> invokeBindBack(screenName, bundle));
58     } catch (RejectedExecutionException e) {
59       LOG.e(String.format("Screen %s bind back fail.", screenName), e);
60     }
61   }
62 
63   /**
64    * Help invoke the {@link ISetupCompatService#onFocusStatusChanged} using {@code loggingExecutor}.
65    */
onFocusStatusChanged(String screenName, Bundle bundle)66   public void onFocusStatusChanged(String screenName, Bundle bundle) {
67     try {
68       loggingExecutor.execute(() -> invokeOnWindowFocusChanged(screenName, bundle));
69     } catch (RejectedExecutionException e) {
70       LOG.e(String.format("Screen %s report focus changed failed.", screenName), e);
71     }
72   }
73 
invokeLogMetric( @etricType int metricType, @SuppressWarnings("unused") Bundle args)74   private void invokeLogMetric(
75       @MetricType int metricType, @SuppressWarnings("unused") Bundle args) {
76     try {
77       ISetupCompatService setupCompatService =
78           SetupCompatServiceProvider.get(
79               context, waitTimeInMillisForServiceConnection, TimeUnit.MILLISECONDS);
80       if (setupCompatService != null) {
81         setupCompatService.logMetric(metricType, args, Bundle.EMPTY);
82       } else {
83         LOG.w("logMetric failed since service reference is null. Are the permissions valid?");
84       }
85     } catch (InterruptedException | TimeoutException | RemoteException | IllegalStateException e) {
86       LOG.e(String.format("Exception occurred while trying to log metric = [%s]", args), e);
87     }
88   }
89 
invokeOnWindowFocusChanged(String screenName, Bundle bundle)90   private void invokeOnWindowFocusChanged(String screenName, Bundle bundle) {
91     try {
92       ISetupCompatService setupCompatService =
93           SetupCompatServiceProvider.get(
94               context, waitTimeInMillisForServiceConnection, TimeUnit.MILLISECONDS);
95       if (setupCompatService != null) {
96         setupCompatService.onFocusStatusChanged(bundle);
97       } else {
98         LOG.w(
99             "Report focusChange failed since service reference is null. Are the permission valid?");
100       }
101     } catch (InterruptedException
102         | TimeoutException
103         | RemoteException
104         | UnsupportedOperationException e) {
105       LOG.e(
106           String.format(
107               "Exception occurred while %s trying report windowFocusChange to SetupWizard.",
108               screenName),
109           e);
110     }
111   }
112 
invokeBindBack(String screenName, Bundle bundle)113   private void invokeBindBack(String screenName, Bundle bundle) {
114     try {
115       ISetupCompatService setupCompatService =
116           SetupCompatServiceProvider.get(
117               context, waitTimeInMillisForServiceConnection, TimeUnit.MILLISECONDS);
118       if (setupCompatService != null) {
119         setupCompatService.validateActivity(screenName, bundle);
120       } else {
121         LOG.w("BindBack failed since service reference is null. Are the permissions valid?");
122       }
123     } catch (InterruptedException | TimeoutException | RemoteException e) {
124       LOG.e(
125           String.format("Exception occurred while %s trying bind back to SetupWizard.", screenName),
126           e);
127     }
128   }
129 
SetupCompatServiceInvoker(Context context)130   private SetupCompatServiceInvoker(Context context) {
131     this.context = context;
132     this.loggingExecutor = ExecutorProvider.setupCompatServiceInvoker.get();
133     this.waitTimeInMillisForServiceConnection = MAX_WAIT_TIME_FOR_CONNECTION_MS;
134   }
135 
136   private final Context context;
137 
138   private final ExecutorService loggingExecutor;
139   private final long waitTimeInMillisForServiceConnection;
140 
get(Context context)141   public static synchronized SetupCompatServiceInvoker get(Context context) {
142     if (instance == null) {
143       instance = new SetupCompatServiceInvoker(context.getApplicationContext());
144     }
145 
146     return instance;
147   }
148 
149   @VisibleForTesting
setInstanceForTesting(SetupCompatServiceInvoker testInstance)150   static void setInstanceForTesting(SetupCompatServiceInvoker testInstance) {
151     instance = testInstance;
152   }
153 
154   // The instance is coming from Application context which alive during the application activate and
155   // it's not depend on the activities life cycle, so we can avoid memory leak. However linter
156   // cannot distinguish Application context or activity context, so we add @SuppressLint to avoid
157   // lint error.
158   @SuppressLint("StaticFieldLeak")
159   private static SetupCompatServiceInvoker instance;
160 
161   private static final long MAX_WAIT_TIME_FOR_CONNECTION_MS = TimeUnit.SECONDS.toMillis(10);
162 }
163