1df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev/*
2df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev * Copyright (C) 2016 The Android Open Source Project
3df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev *
4df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev * Licensed under the Apache License, Version 2.0 (the "License");
5df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev * you may not use this file except in compliance with the License.
6df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev * You may obtain a copy of the License at
7df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev *
8df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev *      http://www.apache.org/licenses/LICENSE-2.0
9df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev *
10df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev * Unless required by applicable law or agreed to in writing, software
11df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev * distributed under the License is distributed on an "AS IS" BASIS,
12df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev * See the License for the specific language governing permissions and
14df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev * limitations under the License.
15df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev */
16df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsevpackage com.android.car.cluster.sample;
17df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev
18df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsevimport static com.android.car.cluster.sample.DebugUtil.DEBUG;
19df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev
20df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsevimport android.annotation.Nullable;
21df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsevimport android.app.Presentation;
22df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsevimport android.car.cluster.renderer.NavigationRenderer;
23df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsevimport android.car.navigation.CarNavigationInstrumentCluster;
243388e7848f3a30029935463afafe9b8280939127Keun-young Parkimport android.car.navigation.CarNavigationStatusManager;
25df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsevimport android.content.ComponentName;
26df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsevimport android.content.ContentResolver;
27df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsevimport android.content.Context;
28df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsevimport android.content.Intent;
29df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsevimport android.content.ServiceConnection;
30df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsevimport android.content.res.Resources;
31df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsevimport android.graphics.Bitmap;
32df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsevimport android.graphics.Color;
33df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsevimport android.hardware.display.DisplayManager;
34df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsevimport android.media.MediaDescription;
35df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsevimport android.media.MediaMetadata;
36df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsevimport android.media.session.PlaybackState;
37df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsevimport android.os.Handler;
38df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsevimport android.os.IBinder;
39df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsevimport android.os.Looper;
40df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsevimport android.os.SystemClock;
41df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsevimport android.os.UserHandle;
42df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsevimport android.provider.Settings;
43df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsevimport android.telecom.Call;
44df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsevimport android.telecom.GatewayInfo;
45a928f13d7e41030331afaad33ee62e3772d96541Pavel Maltsevimport android.text.TextUtils;
46df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsevimport android.util.Log;
47a928f13d7e41030331afaad33ee62e3772d96541Pavel Maltsevimport android.util.SparseArray;
48df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsevimport android.view.Display;
49df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev
50df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsevimport com.android.car.cluster.sample.MediaStateMonitor.MediaStateListener;
51df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsevimport com.android.car.cluster.sample.cards.MediaCard;
52df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsevimport com.android.car.cluster.sample.cards.NavCard;
53df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev
54a928f13d7e41030331afaad33ee62e3772d96541Pavel Maltsevimport java.text.DecimalFormatSymbols;
55a928f13d7e41030331afaad33ee62e3772d96541Pavel Maltsevimport java.text.NumberFormat;
56a928f13d7e41030331afaad33ee62e3772d96541Pavel Maltsevimport java.util.Locale;
57df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsevimport java.util.Objects;
58df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsevimport java.util.Timer;
59df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsevimport java.util.TimerTask;
60df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev
61df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev/**
62df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev * This class is responsible for subscribing to system events (such as call status, media status,
63df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev * etc.) and updating accordingly UI component {@link ClusterView}.
64df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev */
65df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev/*package*/ class InstrumentClusterController {
66df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev
67df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev    private final static String TAG = DebugUtil.getTag(InstrumentClusterController.class);
68df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev
69df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev    private final Context mContext;
70df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev    private final NavigationRenderer mNavigationRenderer;
71a928f13d7e41030331afaad33ee62e3772d96541Pavel Maltsev    private final SparseArray<String> mDistanceUnitNames = new SparseArray<>();
72df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev
73df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev    private ClusterView mClusterView;
74df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev    private MediaStateMonitor mMediaStateMonitor;
75df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev    private MediaStateListenerImpl mMediaStateListener;
76df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev    private ClusterInCallService mInCallService;
77df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev    private MessagingNotificationHandler mNotificationHandler;
78df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev    private StatusBarNotificationListener mNotificationListener;
79df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev    private RetriableServiceBinder mInCallServiceRetriableBinder;
80df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev    private RetriableServiceBinder mNotificationServiceRetriableBinder;
81df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev
82df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev    InstrumentClusterController(Context context) {
83df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev        mContext = context;
84df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev        mNavigationRenderer = new NavigationRendererImpl(this);
85df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev
86df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev        init();
87df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev    }
88df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev
89df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev    private void init() {
90df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev        grantNotificationListenerPermissionsIfNecessary(mContext);
91df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev
92df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev        final Display display = getInstrumentClusterDisplay(mContext);
93df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev        if (DEBUG) {
94df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev            Log.d(TAG, "Instrument cluster display: " + display);
95df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev        }
96df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev        if (display == null) {
97df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev            return;
98df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev        }
99df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev
100a928f13d7e41030331afaad33ee62e3772d96541Pavel Maltsev        initDistanceUnitNames(mContext);
101a928f13d7e41030331afaad33ee62e3772d96541Pavel Maltsev
102df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev        mClusterView = new ClusterView(mContext);
103df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev        Presentation presentation = new InstrumentClusterPresentation(mContext, display);
104df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev        presentation.setContentView(mClusterView);
105df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev
106df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev        // To handle incoming messages
107df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev        mNotificationHandler = new MessagingNotificationHandler(mClusterView);
108df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev
109df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev        mMediaStateListener = new MediaStateListenerImpl(this);
110df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev        mMediaStateMonitor = new MediaStateMonitor(mContext, mMediaStateListener);
111df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev
112df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev        mInCallServiceRetriableBinder = new RetriableServiceBinder(
113df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev                new Handler(Looper.getMainLooper()),
114df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev                mContext,
115df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev                ClusterInCallService.class,
116df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev                ClusterInCallService.ACTION_LOCAL_BINDING,
117df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev                mInCallServiceConnection);
118df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev        mInCallServiceRetriableBinder.attemptToBind();
119df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev
120df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev        mNotificationServiceRetriableBinder = new RetriableServiceBinder(
121df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev                new Handler(Looper.getMainLooper()),
122df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev                mContext,
123df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev                StatusBarNotificationListener.class,
124df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev                StatusBarNotificationListener.ACTION_LOCAL_BINDING,
125df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev                mNotificationListenerConnection);
126df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev        mNotificationServiceRetriableBinder.attemptToBind();
127df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev
128df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev        // Show default card - weather
129df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev        mClusterView.enqueueCard(mClusterView.createWeatherCard());
130df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev
131df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev        presentation.show();
132df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev    }
133df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev
134df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev    NavigationRenderer getNavigationRenderer() {
135df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev        return mNavigationRenderer;
136df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev    }
137df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev
138df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev    private final ServiceConnection mInCallServiceConnection = new ServiceConnection() {
139df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev        @Override
140df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev        public void onServiceConnected(ComponentName name, IBinder binder) {
141df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev            if (DEBUG) {
142df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev                Log.d(TAG, "onServiceConnected, name: " + name + ", binder: " + binder);
143df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev            }
144df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev
145df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev            mInCallService = ((ClusterInCallService.LocalBinder) binder).getService();
146df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev            mInCallService.registerListener(mCallServiceListener);
147df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev
148df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev            // The InCallServiceImpl could be bound when we already have some active calls, let's
149df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev            // notify UI about these calls.
150df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev            for (Call call : mInCallService.getCalls()) {
151df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev                mCallServiceListener.onStateChanged(call, call.getState());
152df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev            }
153df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev            mInCallServiceRetriableBinder = null;
154df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev        }
155df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev
156df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev        @Override
157df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev        public void onServiceDisconnected(ComponentName name) {
158df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev            if (DEBUG) {
159df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev                Log.d(TAG, "onServiceDisconnected, name: " + name);
160df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev            }
161df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev        }
162df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev    };
163df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev
164df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev    private final ServiceConnection mNotificationListenerConnection = new ServiceConnection() {
165df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev        @Override
166df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev        public void onServiceConnected(ComponentName name, IBinder binder) {
167df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev            if (DEBUG) {
168df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev                Log.d(TAG, "onServiceConnected, name: " + name + ", binder: " + binder);
169df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev            }
170df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev
171df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev            mNotificationListener = ((StatusBarNotificationListener.LocalBinder) binder)
172df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev                    .getService();
173df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev            mNotificationListener.setHandler(mNotificationHandler);
174df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev
175df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev            mNotificationServiceRetriableBinder = null;
176df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev        }
177df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev
178df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev        @Override
179df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev        public void onServiceDisconnected(ComponentName name) {
180df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev            if (DEBUG) {
181df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev                Log.d(TAG, "onServiceDisconnected, name: "+ name);
182df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev            }
183df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev        }
184df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev    };
185df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev
186df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev    private final Call.Callback mCallServiceListener = new Call.Callback() {
187df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev        @Override
188df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev        public void onStateChanged(Call call, int state) {
189df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev            if (DEBUG) {
190df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev                Log.d(TAG, "onCallStateChanged, call: " + call + ", state: " + state);
191df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev            }
192df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev
193df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev            runOnMain(() -> InstrumentClusterController.this.onCallStateChanged(call, state));
194df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev        }
195df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev    };
196df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev
197df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev    private String extractPhoneNumber(Call call) {
198df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev        String number = "";
199df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev        Call.Details details = call.getDetails();
200df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev        if (details != null) {
201df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev            GatewayInfo gatewayInfo = details.getGatewayInfo();
202df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev
203df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev            if (gatewayInfo != null) {
204df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev                number = gatewayInfo.getOriginalAddress().getSchemeSpecificPart();
205df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev            } else if (details.getHandle() != null) {
206df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev                number = details.getHandle().getSchemeSpecificPart();
207df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev            }
208df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev        } else {
209df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev            number = mContext.getResources().getString(R.string.unknown);
210df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev        }
211df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev
212df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev        return number;
213df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev    }
214df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev
215a928f13d7e41030331afaad33ee62e3772d96541Pavel Maltsev    private void initDistanceUnitNames(Context context) {
2163388e7848f3a30029935463afafe9b8280939127Keun-young Park        mDistanceUnitNames.put(CarNavigationStatusManager.DISTANCE_METERS,
217a928f13d7e41030331afaad33ee62e3772d96541Pavel Maltsev                context.getString(R.string.nav_distance_units_meters));
2183388e7848f3a30029935463afafe9b8280939127Keun-young Park        mDistanceUnitNames.put(CarNavigationStatusManager.DISTANCE_KILOMETERS,
219a928f13d7e41030331afaad33ee62e3772d96541Pavel Maltsev                context.getString(R.string.nav_distance_units_kilometers));
2203388e7848f3a30029935463afafe9b8280939127Keun-young Park        mDistanceUnitNames.put(CarNavigationStatusManager.DISTANCE_FEET,
221a928f13d7e41030331afaad33ee62e3772d96541Pavel Maltsev                context.getString(R.string.nav_distance_units_ft));
2223388e7848f3a30029935463afafe9b8280939127Keun-young Park        mDistanceUnitNames.put(CarNavigationStatusManager.DISTANCE_MILES,
223a928f13d7e41030331afaad33ee62e3772d96541Pavel Maltsev                context.getString(R.string.nav_distance_units_miles));
2243388e7848f3a30029935463afafe9b8280939127Keun-young Park        mDistanceUnitNames.put(CarNavigationStatusManager.DISTANCE_YARDS,
225a928f13d7e41030331afaad33ee62e3772d96541Pavel Maltsev                context.getString(R.string.nav_distance_units_yards));
226a928f13d7e41030331afaad33ee62e3772d96541Pavel Maltsev    }
227a928f13d7e41030331afaad33ee62e3772d96541Pavel Maltsev
228df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev    private void onCallStateChanged(Call call, int state) {
229df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev        if (DEBUG) {
230df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev            Log.d(TAG, "onCallStateChanged, call: " + call + ", state: " + state);
231df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev        }
232df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev
233df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev        switch (state) {
234df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev            case Call.STATE_ACTIVE: {
235df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev                Call.Details details = call.getDetails();
236df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev                if (details != null) {
237df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev                    long duration = System.currentTimeMillis() - details.getConnectTimeMillis();
238df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev                    mClusterView.handleCallConnected(SystemClock.elapsedRealtime() - duration);
239df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev                }
240df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev            } break;
241df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev            case Call.STATE_CONNECTING: {
242df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev
243df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev            } break;
244df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev            case Call.STATE_DISCONNECTING: {
245df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev                mClusterView.handleCallDisconnected();
246df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev            } break;
247df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev            case Call.STATE_DIALING: {
248df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev                String phoneNumber = extractPhoneNumber(call);
249df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev                String displayName = TelecomUtils.getDisplayName(mContext, phoneNumber);
250df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev                Bitmap image = TelecomUtils
251df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev                        .getContactPhotoFromNumber(mContext.getContentResolver(), phoneNumber);
252df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev                mClusterView.handleDialingCall(image, displayName);
253df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev            } break;
254df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev            case Call.STATE_DISCONNECTED: {
255df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev                mClusterView.handleCallDisconnected();
256df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev            } break;
257df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev            case Call.STATE_HOLDING:
258df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev                break;
259df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev            case Call.STATE_NEW:
260df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev                break;
261df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev            case Call.STATE_RINGING: {
262df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev                String phoneNumber = extractPhoneNumber(call);
263df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev                String displayName = TelecomUtils.getDisplayName(mContext, phoneNumber);
264df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev                Bitmap image = TelecomUtils
265df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev                        .getContactPhotoFromNumber(mContext.getContentResolver(), phoneNumber);
266df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev                if (image != null) {
267df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev                    if (DEBUG) {
268df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev                        Log.d(TAG, "Incoming call, contact image size: " + image.getWidth()
269df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev                                + "x" + image.getHeight());
270df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev                    }
271df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev                }
272df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev                mClusterView.handleIncomingCall(image, displayName);
273df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev            } break;
274df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev            default:
275df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev                Log.w(TAG, "Unexpected call state: " + state + ", call : " + call);
276df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev        }
277df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev    }
278df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev
279df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev    private static void grantNotificationListenerPermissionsIfNecessary(Context context) {
280df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev        ComponentName componentName = new ComponentName(context,
281df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev                StatusBarNotificationListener.class);
282df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev        String componentFlatten = componentName.flattenToString();
283df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev
284df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev        ContentResolver resolver = context.getContentResolver();
285df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev        String grantedComponents = Settings.Secure.getString(resolver,
286df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev                Settings.Secure.ENABLED_NOTIFICATION_LISTENERS);
287df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev
288df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev        if (grantedComponents != null) {
289df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev            String[] allowed = grantedComponents.split(":");
290df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev            for (String s : allowed) {
291df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev                if (s.equals(componentFlatten)) {
292df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev                    if (DEBUG) {
293df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev                        Log.d(TAG, "Notification listener permission granted.");
294df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev                    }
295df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev                    return;  // Permission already granted.
296df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev                }
297df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev            }
298df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev        }
299df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev
300df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev        if (DEBUG) {
301df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev            Log.d(TAG, "Granting notification listener permission.");
302df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev        }
303df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev        Settings.Secure.putString(resolver,
304df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev                Settings.Secure.ENABLED_NOTIFICATION_LISTENERS,
305df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev                grantedComponents + ":" + componentFlatten);
306df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev
307df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev    }
308df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev
309df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev    /* package */ void onDestroy() {
310df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev        if (mMediaStateMonitor != null) {
311df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev            mMediaStateMonitor.release();
312df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev            mMediaStateMonitor = null;
313df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev        }
314df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev        if (mMediaStateListener != null) {
315df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev            mMediaStateListener.release();
316df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev            mMediaStateListener = null;
317df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev        }
318df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev        if (mInCallService != null) {
319df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev            mContext.unbindService(mInCallServiceConnection);
320df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev            mInCallService = null;
321df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev        }
322df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev        if (mNotificationListener != null) {
323df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev            mContext.unbindService(mNotificationListenerConnection);
324df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev            mNotificationListener = null;
325df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev        }
326df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev        if (mInCallServiceRetriableBinder != null) {
327df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev            mInCallServiceRetriableBinder.release();
328df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev            mInCallServiceRetriableBinder = null;
329df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev        }
330df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev        if (mNotificationServiceRetriableBinder != null) {
331df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev            mNotificationServiceRetriableBinder.release();
332df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev            mNotificationServiceRetriableBinder = null;
333df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev        }
334df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev    }
335df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev
336df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev    private static Display getInstrumentClusterDisplay(Context context) {
337df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev        DisplayManager displayManager = context.getSystemService(DisplayManager.class);
338df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev        Display[] displays = displayManager.getDisplays();
339df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev
340df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev        if (DEBUG) {
341df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev            Log.d(TAG, "There are currently " + displays.length + " displays connected.");
342df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev            for (Display display : displays) {
343df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev                Log.d(TAG, "  " + display);
344df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev            }
345df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev        }
346df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev
347df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev        if (displays.length > 1) {
348df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev            // TODO: Put this into settings?
349df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev            return displays[displays.length - 1];
350df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev        }
351df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev        return null;
352df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev    }
353df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev
354df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev    private static void runOnMain(Runnable runnable) {
355df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev        new Handler(Looper.getMainLooper()).post(runnable);
356df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev    }
357df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev
358df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev    private static class MediaStateListenerImpl implements MediaStateListener {
359df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev        private final Timer mTimer = new Timer("ClusterMediaProgress");
360df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev        private final ClusterView mClusterView;
361df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev
362df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev        private MediaData mCurrentMedia;
363df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev        private MediaAppInfo mMediaAppInfo;
364df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev        private MediaCard mCard;
365df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev        private PlaybackState mPlaybackState;
366df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev        private TimerTask mTimerTask;
367df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev
368df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev        MediaStateListenerImpl(InstrumentClusterController renderer) {
369df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev            mClusterView = renderer.mClusterView;
370df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev        }
371df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev
372df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev        void release() {
373df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev            if (mTimerTask != null) {
374df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev                mTimerTask.cancel();
375df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev                mTimerTask = null;
376df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev            }
377df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev        }
378df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev
379df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev        @Override
380df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev        public void onPlaybackStateChanged(final PlaybackState playbackState) {
381df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev            if (DEBUG) {
382df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev                Log.d(TAG, "onPlaybackStateChanged, playbackState: " + playbackState);
383df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev            }
384df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev
385df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev            if (mTimerTask != null) {
386df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev                mTimerTask.cancel();
387df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev                mTimerTask = null;
388df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev            }
389df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev
390df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev            if (playbackState != null) {
391df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev                if ((playbackState.getState() == PlaybackState.STATE_PLAYING
392df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev                            || playbackState.getState() == PlaybackState.STATE_BUFFERING)) {
393df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev                    mPlaybackState = playbackState;
394df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev
395df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev                    if (mCurrentMedia != null) {
396df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev                        showMediaCardIfNecessary(mCurrentMedia);
397df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev
398df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev                        if (mCurrentMedia.duration > 0) {
399df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev                            startTrackProgressTimer();
400df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev                        }
401df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev                    }
402df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev                } else if (playbackState.getState() == PlaybackState.STATE_STOPPED
403df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev                        || playbackState.getState() == PlaybackState.STATE_ERROR
404df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev                        || playbackState.getState() == PlaybackState.STATE_NONE) {
405df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev                    hideMediaCard();
406df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev                }
407df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev            } else {
408df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev                hideMediaCard();
409df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev            }
410df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev
411df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev        }
412df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev
413df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev        private void startTrackProgressTimer() {
414df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev            mTimerTask = new TimerTask() {
415df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev                @Override
416df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev                public void run() {
417df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev                    runOnMain(() -> {
418df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev                        if (mPlaybackState == null || mCard == null) {
419df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev                            return;
420df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev                        }
421df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev                        long trackStarted = mPlaybackState.getLastPositionUpdateTime()
422df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev                                - mPlaybackState.getPosition();
423df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev                        long trackDuration = mCurrentMedia == null ? 0 : mCurrentMedia.duration;
424df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev
425df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev                        long currentTime = SystemClock.elapsedRealtime();
426df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev                        long progressMs = (currentTime - trackStarted);
427df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev                        if (trackDuration > 0) {
428df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev                            mCard.setProgress((int)((progressMs * 100) / trackDuration));
429df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev                        }
430df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev                    });
431df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev                }
432df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev            };
433df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev
434df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev            mTimer.scheduleAtFixedRate(mTimerTask, 0, 1000);
435df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev        }
436df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev
437df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev
438df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev        @Override
439df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev        public void onMetadataChanged(MediaMetadata metadata) {
440df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev            if (DEBUG) {
441df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev                Log.d(TAG, "onMetadataChanged: " + metadata);
442df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev            }
443df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev            MediaData data = MediaData.createFromMetadata(metadata);
444df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev            if (data == null) {
445df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev                hideMediaCard();
446df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev            }
447df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev            mCurrentMedia = data;
448df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev        }
449df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev
450df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev        private void hideMediaCard() {
451df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev            if (DEBUG) {
452df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev                Log.d(TAG, "hideMediaCard");
453df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev            }
454df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev
455df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev            if (mCard != null) {
456df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev                mClusterView.removeCard(mCard);
457df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev                mCard = null;
458df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev            }
459df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev
460df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev            // Remove all existing media cards if any.
461df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev            MediaCard mediaCard;
462df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev            do {
463df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev                mediaCard = mClusterView.getCardOrNull(MediaCard.class);
464df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev                if (mediaCard != null) {
465df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev                    mClusterView.removeCard(mediaCard);
466df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev                }
467df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev            } while (mediaCard != null);
468df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev        }
469df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev
470df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev        private void showMediaCardIfNecessary(MediaData data) {
471df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev            if (!needToCreateMediaCard(data)) {
472df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev                return;
473df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev            }
474df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev
475df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev            int accentColor = mMediaAppInfo == null
476df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev                    ? Color.GRAY : mMediaAppInfo.getMediaClientAccentColor();
477df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev
478df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev            mCard = mClusterView.createMediaCard(
479df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev                    data.albumCover, data.title, data.subtitle, accentColor);
480df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev            if (data.duration <= 0) {
481df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev                mCard.setProgress(100); // unknown position
482df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev            } else {
483df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev                mCard.setProgress(0);
484df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev            }
485df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev            mClusterView.enqueueCard(mCard);
486df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev        }
487df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev
488df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev        private boolean needToCreateMediaCard(MediaData data) {
489df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev            return (mCard == null)
490df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev                    || !Objects.equals(mCard.getTitle(), data.title)
491df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev                    || !Objects.equals(mCard.getSubtitle(), data.subtitle);
492df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev        }
493df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev
494df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev        @Override
495df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev        public void onMediaAppChanged(MediaAppInfo mediaAppInfo) {
496df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev            mMediaAppInfo = mediaAppInfo;
497df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev        }
498df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev
499df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev        private static class MediaData {
500df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev            final Bitmap albumCover;
501df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev            final String subtitle;
502df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev            final String title;
503df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev            final long duration;
504df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev
505df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev            private MediaData(MediaMetadata metadata) {
506df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev                MediaDescription mediaDescription = metadata.getDescription();
507df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev                title = charSequenceToString(mediaDescription.getTitle());
508df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev                subtitle = charSequenceToString(mediaDescription.getSubtitle());
509df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev                albumCover = mediaDescription.getIconBitmap();
510df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev                duration = metadata.getLong(MediaMetadata.METADATA_KEY_DURATION);
511df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev            }
512df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev
513df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev            static MediaData createFromMetadata(MediaMetadata metadata) {
514df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev                return  metadata == null ? null : new MediaData(metadata);
515df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev            }
516df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev
517df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev            private static String charSequenceToString(@Nullable CharSequence cs) {
518df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev                return cs == null ? null : String.valueOf(cs);
519df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev            }
520df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev
521df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev            @Override
522df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev            public String toString() {
523df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev                return "MediaData{" +
524df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev                        "albumCover=" + albumCover +
525df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev                        ", subtitle='" + subtitle + '\'' +
526df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev                        ", title='" + title + '\'' +
527df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev                        ", duration=" + duration +
528df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev                        '}';
529df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev            }
530df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev        }
531df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev    }
532df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev
533df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev    private static class NavigationRendererImpl extends NavigationRenderer {
534df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev
535df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev        private final InstrumentClusterController mController;
536df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev
537df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev        private ClusterView mClusterView;
538df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev        private Resources mResources;
539df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev
540df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev        private NavCard mNavCard;
541df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev
542df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev        NavigationRendererImpl(InstrumentClusterController controller) {
543df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev            mController = controller;
544df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev        }
545df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev
546df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev        @Override
547df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev        public CarNavigationInstrumentCluster getNavigationProperties() {
548df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev            if (DEBUG) {
549df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev                Log.d(TAG, "getNavigationProperties");
550df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev            }
551df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev            return CarNavigationInstrumentCluster.createCustomImageCluster(
552df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev                    1000, /* 1 Hz*/
553df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev                    64,   /* image width */
554df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev                    64,   /* image height */
555df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev                    32);  /* color depth */
556df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev        }
557df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev
558df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev        @Override
559df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev        public void onStartNavigation() {
560df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev            if (DEBUG) {
561df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev                Log.d(TAG, "onStartNavigation");
562df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev            }
563df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev            mClusterView = mController.mClusterView;
564df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev            mResources = mController.mContext.getResources();
565df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev            mNavCard = mClusterView.createNavCard();
566df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev        }
567df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev
568df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev        @Override
569df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev        public void onStopNavigation() {
570df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev            if (DEBUG) {
571df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev                Log.d(TAG, "onStopNavigation");
572df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev            }
573df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev
574df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev            if (mNavCard != null) {
575df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev                mNavCard.removeGracefully();
576df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev                mNavCard = null;
577df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev            }
578df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev        }
579df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev
580df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev        @Override
5813388e7848f3a30029935463afafe9b8280939127Keun-young Park        public void onNextTurnChanged(int event, CharSequence eventName, int turnAngle,
5823388e7848f3a30029935463afafe9b8280939127Keun-young Park                int turnNumber, Bitmap image, int turnSide) {
583df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev            if (DEBUG) {
5843388e7848f3a30029935463afafe9b8280939127Keun-young Park                Log.d(TAG, "onNextTurnChanged, eventName: " + eventName + ", image: " + image +
585df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev                        (image == null ? "" : ", size: "
586df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev                                + image.getWidth() + "x" + image.getHeight()));
587df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev            }
588df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev            mNavCard.setManeuverImage(BitmapUtils.generateNavManeuverIcon(
589df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev                    (int) mResources.getDimension(R.dimen.card_icon_size),
590df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev                    mResources.getColor(R.color.maps_background, null),
591df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev                    image));
5923388e7848f3a30029935463afafe9b8280939127Keun-young Park            mNavCard.setStreet(eventName);
593df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev            if (!mClusterView.cardExists(mNavCard)) {
594df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev                mClusterView.enqueueCard(mNavCard);
595df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev            }
596df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev        }
597df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev
598df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev        @Override
599a928f13d7e41030331afaad33ee62e3772d96541Pavel Maltsev        public void onNextTurnDistanceChanged(int meters, int timeSeconds,
6003388e7848f3a30029935463afafe9b8280939127Keun-young Park                int displayDistanceMillis, int distanceUnit) {
601df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev            if (DEBUG) {
602df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev                Log.d(TAG, "onNextTurnDistanceChanged, distanceMeters: " + meters
603a928f13d7e41030331afaad33ee62e3772d96541Pavel Maltsev                        + ", timeSeconds: " + timeSeconds
604a928f13d7e41030331afaad33ee62e3772d96541Pavel Maltsev                        + ", displayDistanceMillis: " + displayDistanceMillis
6053388e7848f3a30029935463afafe9b8280939127Keun-young Park                        + ", DistanceUnit: " + distanceUnit);
606df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev            }
607df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev
608a928f13d7e41030331afaad33ee62e3772d96541Pavel Maltsev            int remainder = displayDistanceMillis % 1000;
6093388e7848f3a30029935463afafe9b8280939127Keun-young Park            String decimalPart = (remainder != 0)
610a928f13d7e41030331afaad33ee62e3772d96541Pavel Maltsev                    ? String.format("%c%d",
611a928f13d7e41030331afaad33ee62e3772d96541Pavel Maltsev                                    DecimalFormatSymbols.getInstance().getDecimalSeparator(),
612a928f13d7e41030331afaad33ee62e3772d96541Pavel Maltsev                                    remainder)
613a928f13d7e41030331afaad33ee62e3772d96541Pavel Maltsev                    : "";
614a928f13d7e41030331afaad33ee62e3772d96541Pavel Maltsev
615a928f13d7e41030331afaad33ee62e3772d96541Pavel Maltsev            String distanceToDisplay = (displayDistanceMillis / 1000) + decimalPart;
6163388e7848f3a30029935463afafe9b8280939127Keun-young Park            String unitsToDisplay = mController.mDistanceUnitNames.get(distanceUnit);
617a928f13d7e41030331afaad33ee62e3772d96541Pavel Maltsev
618a928f13d7e41030331afaad33ee62e3772d96541Pavel Maltsev            mNavCard.setDistanceToNextManeuver(distanceToDisplay, unitsToDisplay);
619df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev        }
620df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev    }
621df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev
622df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev    /**
623df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev     * Services might not be ready for binding. This class will retry binding after short interval
624df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev     * if previous binding failed.
625df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev     */
626df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev    private static class RetriableServiceBinder {
627df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev        private static final long RETRY_INTERVAL_MS = 500;
628df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev        private static final long MAX_RETRY = 30;
629df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev
630df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev        private Handler mHandler;
631df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev        private final Context mContext;
632df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev        private final Intent mIntent;
633df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev        private final ServiceConnection mConnection;
634df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev
635df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev        private long mAttemptsLeft = MAX_RETRY;
636df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev
637df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev        private final Runnable mBindRunnable = () -> attemptToBind();
638df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev
639df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev        RetriableServiceBinder(Handler handler, Context context, Class<?> cls, String action,
640df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev                ServiceConnection connection) {
641df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev            mHandler = handler;
642df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev            mContext = context;
643df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev            mIntent = new Intent(mContext, cls);
644df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev            mIntent.setAction(action);
645df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev            mConnection = connection;
646df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev        }
647df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev
648df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev        void release() {
649df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev            mHandler.removeCallbacks(mBindRunnable);
650df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev        }
651df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev
652df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev        void attemptToBind() {
653df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev            boolean bound = mContext.bindServiceAsUser(mIntent,
654df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev                    mConnection, Context.BIND_AUTO_CREATE, UserHandle.CURRENT_OR_SELF);
655df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev
656df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev            if (!bound && --mAttemptsLeft > 0) {
657df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev                mHandler.postDelayed(mBindRunnable, RETRY_INTERVAL_MS);
658df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev            } else if (!bound) {
659df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev                Log.e(TAG, "Gave up to bind to a service: " + mIntent.getComponent() + " after "
660df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev                        + MAX_RETRY + " retries.");
661df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev            }
662df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev        }
663df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev    }
664df4db1d3efb85be01f0973067c9e104b5f93f205Pavel Maltsev}
665