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 }