1 /*
2  * Copyright (c) 2016, 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 package com.android.car.overview;
17 
18 import android.app.Activity;
19 import android.content.ComponentName;
20 import android.content.Context;
21 import android.content.Intent;
22 import android.content.ServiceConnection;
23 import android.graphics.Canvas;
24 import android.graphics.Rect;
25 import android.os.Bundle;
26 import android.os.Handler;
27 import android.os.IBinder;
28 import android.os.RemoteException;
29 import android.support.car.ui.PagedListView;
30 import android.support.v7.widget.RecyclerView;
31 import android.util.Log;
32 import android.view.Gravity;
33 import android.view.View;
34 import android.widget.FrameLayout;
35 import android.widget.Toast;
36 import com.android.car.stream.IStreamConsumer;
37 import com.android.car.stream.IStreamService;
38 import com.android.car.stream.StreamCard;
39 import com.android.car.stream.StreamConstants;
40 
41 import java.util.List;
42 
43 /**
44  * An overview activity that presents {@link StreamCard} as scrollable list.
45  */
46 public class StreamOverviewActivity extends Activity {
47     private static final String TAG = "Overview";
48     private static final int SERVICE_CONNECTION_RETRY_DELAY_MS = 5000;
49     private static final String ACTION_CAR_OVERVIEW_STATE_CHANGE
50             = "android.intent.action.CAR_OVERVIEW_APP_STATE_CHANGE";
51     private static final String EXTRA_CAR_OVERVIEW_FOREGROUND
52             = "android.intent.action.CAR_APP_STATE";
53 
54     private static final int PERMISSION_ACTIVITY_REQUEST_CODE = 5151;
55 
56     private PagedListView mPageListView;
57     private View mPermissionText;
58     private StreamAdapter mAdapter;
59     private final Handler mHandler = new Handler();
60     private int mConnectionRetryCount;
61 
62     private IStreamService mService;
63     private StreamServiceConnection mConnection;
64 
65     private int mCardBottomMargin;
66     private Toast mToast;
67 
68     boolean mCheckPermissionsOnResume;
69 
70     @Override
onCreate(Bundle savedInstanceState)71     protected void onCreate(Bundle savedInstanceState) {
72         super.onCreate(savedInstanceState);
73         setContentView(R.layout.overview_activity);
74         mCardBottomMargin = getResources().getDimensionPixelSize(R.dimen.stream_card_bottom_margin);
75 
76         int statusBarHeight = getStatusBarHeight();
77 
78         FrameLayout.LayoutParams params
79                 = (FrameLayout.LayoutParams) findViewById(R.id.action_icon_bar).getLayoutParams();
80         params.setMargins(0, statusBarHeight, 0, 0);
81 
82         mAdapter = new StreamAdapter(this /* context */);
83 
84         mPageListView = (PagedListView) findViewById(R.id.list_view);
85         mPageListView.setAdapter(mAdapter);
86         mPageListView.setDefaultItemDecoration(new DefaultDecoration(this /* context */));
87         mPageListView.setLightMode();
88 
89         int listTopMargin = statusBarHeight
90                 + getResources().getDimensionPixelSize(R.dimen.lens_header_height);
91         FrameLayout.LayoutParams listViewParams
92                 = (FrameLayout.LayoutParams) mPageListView.getLayoutParams();
93         listViewParams.setMargins(0, listTopMargin, 0, 0);
94 
95         mPermissionText = findViewById(R.id.permission_text);
96         mPermissionText.setOnClickListener(new View.OnClickListener() {
97             @Override
98             public void onClick(View v) {
99                 startPermissionsActivity(false /* checkPermissionsOnly */);
100             }
101         });
102 
103         mToast = Toast.makeText(StreamOverviewActivity.this,
104                 getString(R.string.voice_assistant_help_msg), Toast.LENGTH_SHORT);
105         mToast.setGravity(Gravity.CENTER, 0, 0);
106         findViewById(R.id.voice_button).setOnClickListener(new View.OnClickListener() {
107             @Override
108             public void onClick(View v) {
109                 mToast.show();
110             }
111         });
112 
113         findViewById(R.id.gear_button).setOnClickListener(new View.OnClickListener() {
114             @Override
115             public void onClick(View v) {
116                 startActivity(new Intent(android.provider.Settings.ACTION_SETTINGS));
117             }
118         });
119 
120         startPermissionsActivity(true /* checkPermissionsOnly */);
121     }
122 
startPermissionsActivity(boolean checkPermissionsOnly)123     private void startPermissionsActivity(boolean checkPermissionsOnly) {
124         // Start StreamService's permission activity before binding to it.
125         Intent intent = new Intent();
126         intent.setComponent(new ComponentName(
127                 getString(R.string.car_stream_item_manager_package_name),
128                 getString(R.string.car_stream_item_manager_permissions_activity)));
129         intent.putExtra(StreamConstants.STREAM_PERMISSION_CHECK_PERMISSIONS_ONLY,
130                 checkPermissionsOnly);
131         startActivityForResult(intent, PERMISSION_ACTIVITY_REQUEST_CODE);
132     }
133 
134     @Override
onResume()135     protected void onResume() {
136         super.onResume();
137         if (mCheckPermissionsOnResume) {
138             startPermissionsActivity(true /* checkPermissionsOnly */);
139         } else {
140             mCheckPermissionsOnResume = true;
141         }
142     }
143 
144     @Override
onActivityResult(int requestCode, int resultCode, Intent data)145     protected void onActivityResult(int requestCode, int resultCode, Intent data) {
146         if (requestCode == PERMISSION_ACTIVITY_REQUEST_CODE) {
147             // onResume is called after onActivityResult, if the permissions activity has
148             // already finished, then don't bother checking for permissions again on resume.
149             mCheckPermissionsOnResume = false;
150             if (resultCode == Activity.RESULT_OK) {
151                 mPermissionText.setVisibility(View.GONE);
152                 mPageListView.setVisibility(View.VISIBLE);
153                 bindStreamService();
154             } else {
155                 mPermissionText.setVisibility(View.VISIBLE);
156                 mPageListView.setVisibility(View.GONE);
157             }
158         }
159     }
160 
161     @Override
onDestroy()162     protected void onDestroy() {
163         // Do a tear down to avoid leaks
164         mHandler.removeCallbacks(mServiceConnectionRetry);
165 
166         if (mConnection != null) {
167             unbindService(mConnection);
168         }
169         super.onDestroy();
170     }
171 
172     @Override
onStart()173     protected void onStart() {
174         super.onStart();
175         Intent i = new Intent(ACTION_CAR_OVERVIEW_STATE_CHANGE);
176         i.putExtra(EXTRA_CAR_OVERVIEW_FOREGROUND, true);
177         sendBroadcast(i);
178     }
179 
180     @Override
onStop()181     protected void onStop() {
182         Intent i = new Intent(ACTION_CAR_OVERVIEW_STATE_CHANGE);
183         i.putExtra(EXTRA_CAR_OVERVIEW_FOREGROUND, false);
184         sendBroadcast(i);
185         super.onStop();
186     }
187 
188     private class StreamServiceConnection implements ServiceConnection {
189         @Override
onServiceConnected(ComponentName name, IBinder service)190         public void onServiceConnected(ComponentName name, IBinder service) {
191             mConnectionRetryCount = 1;
192             // If there is currently a retry scheduled, cancel it.
193             if (mServiceConnectionRetry != null) {
194                 mHandler.removeCallbacks(mServiceConnectionRetry);
195             }
196 
197             mService = IStreamService.Stub.asInterface(service);
198             if (Log.isLoggable(TAG, Log.DEBUG)) {
199                 Log.d(TAG, "Service connected");
200             }
201             try {
202                 mService.registerConsumer(mStreamConsumer);
203 
204                 List<StreamCard> cards = mService.fetchAllStreamCards();
205                 if (cards != null) {
206                     for (StreamCard card : cards) {
207                         mAdapter.addCard(card);
208                     }
209                 }
210 
211 
212             } catch (RemoteException e) {
213                 throw new IllegalStateException("not connected to IStreamItemManagerService");
214             }
215         }
216 
217         @Override
onServiceDisconnected(ComponentName name)218         public void onServiceDisconnected(ComponentName name) {
219             mService = null;
220             mAdapter.removeAllCards();
221             if (Log.isLoggable(TAG, Log.DEBUG)) {
222                 Log.d(TAG, "Service disconnected, reconnecting...");
223             }
224 
225             mHandler.removeCallbacks(mServiceConnectionRetry);
226             mHandler.postDelayed(mServiceConnectionRetry, SERVICE_CONNECTION_RETRY_DELAY_MS);
227         }
228     }
229 
230     private Runnable mServiceConnectionRetry = new Runnable() {
231         @Override
232         public void run() {
233             if (mService != null) {
234                 if (Log.isLoggable(TAG, Log.DEBUG)) {
235                     Log.d(TAG, "Stream service rebound by framework, no need to bind again");
236                 }
237                 return;
238             }
239             mConnectionRetryCount++;
240             if (Log.isLoggable(TAG, Log.DEBUG)) {
241                 Log.d(TAG, "Rebinding disconnected Stream Service, retry count: "
242                         + mConnectionRetryCount);
243             }
244 
245             if (!bindStreamService()) {
246                 mHandler.postDelayed(mServiceConnectionRetry,
247                         mConnectionRetryCount * SERVICE_CONNECTION_RETRY_DELAY_MS);
248             }
249         }
250     };
251 
252     private final IStreamConsumer mStreamConsumer = new IStreamConsumer.Stub() {
253         @Override
254         public void onStreamCardAdded(StreamCard card) throws RemoteException {
255             StreamOverviewActivity.this.runOnUiThread(new Runnable() {
256                 public void run() {
257                     if (Log.isLoggable(TAG, Log.DEBUG)) {
258                         Log.d(TAG, "Stream Card added: " + card);
259                     }
260                     mAdapter.addCard(card);
261                 }
262             });
263         }
264 
265         @Override
266         public void onStreamCardRemoved(StreamCard card) throws RemoteException {
267             StreamOverviewActivity.this.runOnUiThread(new Runnable() {
268                 public void run() {
269                     if (Log.isLoggable(TAG, Log.DEBUG)) {
270                         Log.d(TAG, "Stream Card removed: " + card);
271                     }
272                     mAdapter.removeCard(card);
273                 }
274             });
275         }
276 
277         @Override
278         public void onStreamCardChanged(StreamCard newStreamCard) throws RemoteException {
279         }
280     };
281 
bindStreamService()282     private boolean bindStreamService() {
283         mConnection = new StreamServiceConnection();
284         Intent intent = new Intent();
285         intent.setAction(StreamConstants.STREAM_CONSUMER_BIND_ACTION);
286         intent.setComponent(new ComponentName(
287                 getString(R.string.car_stream_item_manager_package_name),
288                 getString(R.string.car_stream_item_manager_class_name)));
289 
290         boolean bound = bindService(intent, mConnection, BIND_AUTO_CREATE);
291         if (Log.isLoggable(TAG, Log.DEBUG)) {
292             Log.d(TAG, "IStreamItemManagerService bound: " + bound
293                     + "; component: " + intent.getComponent());
294         }
295 
296         return bound;
297     }
298 
299     private class DefaultDecoration extends PagedListView.Decoration {
DefaultDecoration(Context context)300         public DefaultDecoration(Context context) {
301             super(context);
302         }
303 
304         @Override
onDrawOver(Canvas c, RecyclerView parent, RecyclerView.State state)305         public void onDrawOver(Canvas c, RecyclerView parent, RecyclerView.State state) {}
306 
307         @Override
getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state)308         public void getItemOffsets(Rect outRect, View view, RecyclerView parent,
309                 RecyclerView.State state) {
310             outRect.bottom = mCardBottomMargin;
311         }
312     }
313 
getStatusBarHeight()314     private int getStatusBarHeight() {
315         int result = 0;
316         int resourceId = getResources().getIdentifier("status_bar_height", "dimen", "android");
317         if (resourceId > 0) {
318             result = getResources().getDimensionPixelSize(resourceId);
319         }
320         return result;
321     }
322 }