1/*
2 * Copyright (C) 2015 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
17package android.support.car.app;
18
19import android.app.Activity;
20import android.content.Context;
21import android.content.Intent;
22import android.content.res.Configuration;
23import android.content.res.Resources;
24import android.os.Bundle;
25import android.os.Handler;
26import android.support.annotation.LayoutRes;
27import android.support.car.Car;
28import android.support.car.app.menu.CarDrawerActivity;
29import android.support.car.input.CarInputManager;
30import android.util.AttributeSet;
31import android.util.Log;
32import android.view.LayoutInflater;
33import android.view.Menu;
34import android.view.MenuInflater;
35import android.view.View;
36import android.view.Window;
37
38/**
39 * A car specific activity class.  It allows an application to run on both "projected" and
40 * "native" platforms.  For the phone-only mode we only support media and messaging apps at this
41 * time. Please see our guides for writing
42 * <a href="https://developer.android.com/training/auto/index.html#media">media</a> and
43 * <a href="https://developer.android.com/training/auto/index.html#messaging">messaging</a> apps.
44 * <ul>
45 *     <li>
46 *         For "native" systems you'll additionally need to implement a
47 *         {@link CarProxyActivity} and add it to your application's manifest (see
48 *         {@link CarProxyActivity}) for details).
49 *     </li>
50 *     <li>
51 *         For "projected" systems you'll need to implement a
52 *         {@link com.google.android.apps.auto.sdk.activity.CarProxyProjectionActivityService}
53 *     </li>
54 * </ul>
55 *
56 * Applications wishing to write Android Auto applications will need to extend this class or one of
57 * it's sub classes. You'll most likely want to use {@link CarFragmentActivity} or
58 * {@link CarDrawerActivity} instead or this class as this one does not support fragments.
59 * <p/>
60 * This class has the look and feel of {@link Activity} however, it does not extend {@link Activity}
61 * or {@link Context}. Applications should use {@link #getContext()} to access the {@link Context}.
62 *
63 * @hide
64 */
65public abstract class CarActivity {
66    private static final String TAG = "CarActivity";
67    public interface RequestPermissionsRequestCodeValidator {
68        public void validateRequestPermissionsRequestCode(int requestCode);
69    }
70    /**
71     * Interface to connect {@link CarActivity} to {@link android.app.Activity} or other app model.
72     * This interface provides utility for {@link CarActivity} to do things like manipulating view,
73     * handling menu, and etc.
74     */
75    public abstract static class Proxy {
76        abstract public void setIntent(Intent i);
77        abstract public void setContentView(View view);
78        abstract public void setContentView(int layoutResID);
79        abstract public Resources getResources();
80        abstract public View findViewById(int id);
81        abstract public LayoutInflater getLayoutInflater();
82        abstract public Intent getIntent();
83        abstract public void finish();
84        abstract public CarInputManager getCarInputManager();
85        abstract public boolean isFinishing();
86        abstract public MenuInflater getMenuInflater();
87        abstract public void finishAfterTransition();
88        abstract public Window getWindow();
89        abstract public void setResult(int resultCode);
90        abstract public void setResult(int resultCode, Intent data);
91
92        public void requestPermissions(String[] permissions, int requestCode) {
93            Log.w(TAG, "No support for requestPermissions");
94        }
95        public boolean shouldShowRequestPermissionRationale(String permission) {
96            Log.w(TAG, "No support for shouldShowRequestPermissionRationale");
97            return false;
98        }
99        public void startActivityForResult(Intent intent, int requestCode) {
100            Log.w(TAG, "No support for startActivityForResult");
101        };
102    }
103
104    /** @hide */
105    public static final int CMD_ON_CREATE = 0;
106    /** @hide */
107    public static final int CMD_ON_START = 1;
108    /** @hide */
109    public static final int CMD_ON_RESTART = 2;
110    /** @hide */
111    public static final int CMD_ON_RESUME = 3;
112    /** @hide */
113    public static final int CMD_ON_PAUSE = 4;
114    /** @hide */
115    public static final int CMD_ON_STOP = 5;
116    /** @hide */
117    public static final int CMD_ON_DESTROY = 6;
118    /** @hide */
119    public static final int CMD_ON_BACK_PRESSED = 7;
120    /** @hide */
121    public static final int CMD_ON_SAVE_INSTANCE_STATE = 8;
122    /** @hide */
123    public static final int CMD_ON_RESTORE_INSTANCE_STATE = 9;
124    /** @hide */
125    public static final int CMD_ON_CONFIG_CHANGED = 10;
126    /** @hide */
127    public static final int CMD_ON_REQUEST_PERMISSIONS_RESULT = 11;
128    /** @hide */
129    public static final int CMD_ON_NEW_INTENT = 12;
130    /** @hide */
131    public static final int CMD_ON_ACTIVITY_RESULT = 13;
132    /** @hide */
133    public static final int CMD_ON_POST_RESUME = 14;
134    /** @hide */
135    public static final int CMD_ON_LOW_MEMORY = 15;
136
137    private final Proxy mProxy;
138    private final Context mContext;
139    private final Car mCar;
140    private final Handler mHandler = new Handler();
141
142    public CarActivity(Proxy proxy, Context context, Car car) {
143        mProxy = proxy;
144        mContext = context;
145        mCar = car;
146    }
147
148    /**
149     * Returns a standard app {@link Context} object since this class does not extend {@link
150     * Context} link {@link Activity} does.
151     */
152    public Context getContext() {
153        return mContext;
154    }
155
156    /**
157     * Returns an instance of the {@link Car} object.  This is the main entry point to interact
158     * with the car and it's data.
159     *
160     * <p/>
161     * Note: For "native" platform uses cases you'll need to construct the CarProxy activity with
162     * the createCar boolean set to true if you want to use the getCar() method.
163     * <pre>
164     * {@code
165     *
166     *   class FooProxyActivity extends CarProxyActivity {
167     *     public FooProxyActivity() {
168     *       super(FooActivity.class, true);
169     *     }
170     *   }
171     * }
172     * </pre>
173     *
174     * "Projected" use cases will create a Car instance by default.
175     *
176     * @throws IllegalStateException if the Car object is not available.
177     */
178    public Car getCar() {
179        if (mCar == null) {
180            throw new IllegalStateException("The default Car is not available. You can either " +
181                    "create a Car by yourself or indicate the need of the default Car in the " +
182                    "CarProxyActivity's constructor.");
183        }
184        return mCar;
185    }
186
187    /**
188     * Returns the input manager for car activities.
189     */
190    public CarInputManager getInputManager() {
191        return mProxy.getCarInputManager();
192    }
193
194    /**
195     * See {@link Activity#getResources()}.
196     */
197    public Resources getResources() {
198        return mProxy.getResources();
199    }
200
201    /**
202     * See {@link Activity#setContentView(View)}.
203     */
204    public void setContentView(View view) {
205        mProxy.setContentView(view);
206    }
207
208    /**
209     * See {@link Activity#setContentView(int)}.
210     */
211    public void setContentView(@LayoutRes int resourceId) {
212        mProxy.setContentView(resourceId);
213    }
214
215    /**
216     * See {@link Activity#getLayoutInflater()}.
217     */
218    public LayoutInflater getLayoutInflater() {
219        return mProxy.getLayoutInflater();
220    }
221
222    /**
223     * See {@link Activity#getIntent()}
224     */
225    public Intent getIntent() {
226        return mProxy.getIntent();
227    }
228
229    /**
230     * See {@link Activity#setResult(int)}  }
231     */
232    public void setResult(int resultCode){
233        mProxy.setResult(resultCode);
234    }
235
236
237    /**
238     * See {@link Activity#setResult(int, Intent)}  }
239     */
240    public void setResult(int resultCode, Intent data) {
241        mProxy.setResult(resultCode, data);
242    }
243
244    /**
245     * See {@link Activity#findViewById(int)}  }
246     */
247    public View findViewById(int id) {
248        return mProxy.findViewById(id);
249    }
250
251    /**
252     * See {@link Activity#finish()}
253     */
254    public void finish() {
255        mProxy.finish();
256    }
257
258    /**
259     * See {@link Activity#isFinishing()}
260     */
261    public boolean isFinishing() {
262        return mProxy.isFinishing();
263    }
264
265    /**
266     * See {@link Activity#shouldShowRequestPermissionRationale(String)}
267     */
268    public boolean shouldShowRequestPermissionRationale(String permission) {
269        return mProxy.shouldShowRequestPermissionRationale(permission);
270    }
271
272    /**
273     * See {@link Activity#requestPermissions(String[], int)}
274     */
275    public void requestPermissions(String[] permissions, int requestCode) {
276        if (this instanceof RequestPermissionsRequestCodeValidator) {
277            ((RequestPermissionsRequestCodeValidator) this)
278                    .validateRequestPermissionsRequestCode(requestCode);
279        }
280        mProxy.requestPermissions(permissions, requestCode);
281    }
282
283    /**
284     * See {@link Activity#setIntent(Intent)}
285     */
286    public void setIntent(Intent i) {
287        mProxy.setIntent(i);
288    }
289
290    /**
291     * See {@link Activity#finishAfterTransition()}
292     */
293    public void finishAfterTransition() {
294        mProxy.finishAfterTransition();
295    }
296
297    /**
298     * See {@link Activity#getMenuInflater()}
299     */
300    public MenuInflater getMenuInflater() {
301        return mProxy.getMenuInflater();
302    }
303
304    /**
305     * See {@link Activity#getWindow()}
306     */
307    public Window getWindow() {
308        return mProxy.getWindow();
309    }
310
311    /**
312     * See {@link Activity#getLastNonConfigurationInstance()}
313     */
314    public Object getLastNonConfigurationInstance() {
315        return null;
316    }
317
318    /**
319     * See {@link Activity#startActivityForResult(Intent, int)}
320     */
321    public void startActivityForResult(Intent intent, int requestCode) {
322        mProxy.startActivityForResult(intent, requestCode);
323    }
324
325    /**
326     * See {@link Activity#runOnUiThread(Runnable)}
327     */
328    public void runOnUiThread(Runnable runnable) {
329        if (Thread.currentThread() == mHandler.getLooper().getThread()) {
330            runnable.run();
331        } else {
332            mHandler.post(runnable);
333        }
334    }
335
336    /** @hide */
337    public void dispatchCmd(int cmd, Object... args) {
338
339        switch (cmd) {
340            case CMD_ON_CREATE:
341                assertArgsLength(1, args);
342                onCreate((Bundle) args[0]);
343                break;
344            case CMD_ON_START:
345                onStart();
346                break;
347            case CMD_ON_RESTART:
348                onRestart();
349                break;
350            case CMD_ON_RESUME:
351                onResume();
352                break;
353            case CMD_ON_POST_RESUME:
354                onPostResume();
355                break;
356            case CMD_ON_PAUSE:
357                onPause();
358                break;
359            case CMD_ON_STOP:
360                onStop();
361                break;
362            case CMD_ON_DESTROY:
363                onDestroy();
364                break;
365            case CMD_ON_BACK_PRESSED:
366                onBackPressed();
367                break;
368            case CMD_ON_SAVE_INSTANCE_STATE:
369                assertArgsLength(1, args);
370                onSaveInstanceState((Bundle) args[0]);
371                break;
372            case CMD_ON_RESTORE_INSTANCE_STATE:
373                assertArgsLength(1, args);
374                onRestoreInstanceState((Bundle) args[0]);
375                break;
376            case CMD_ON_REQUEST_PERMISSIONS_RESULT:
377                assertArgsLength(3, args);
378                onRequestPermissionsResult(((Integer) args[0]).intValue(),
379                        (String[]) args[1], convertArray((Integer[]) args[2]));
380                break;
381            case CMD_ON_CONFIG_CHANGED:
382                assertArgsLength(1, args);
383                onConfigurationChanged((Configuration) args[0]);
384                break;
385            case CMD_ON_NEW_INTENT:
386                assertArgsLength(1, args);
387                onNewIntent((Intent) args[0]);
388                break;
389            case CMD_ON_ACTIVITY_RESULT:
390                assertArgsLength(3, args);
391                onActivityResult(((Integer) args[0]).intValue(), ((Integer) args[1]).intValue(),
392                        (Intent) args[2]);
393                break;
394            case CMD_ON_LOW_MEMORY:
395                onLowMemory();
396                break;
397            default:
398                throw new RuntimeException("Unknown dispatch cmd for CarActivity, " + cmd);
399        }
400
401    }
402
403    /**
404     * See {@link Activity#onCreate(Bundle)}
405     */
406    protected void onCreate(Bundle savedInstanceState) {
407    }
408
409    /**
410     * See {@link Activity#onStart()}
411     */
412    protected void onStart() {
413    }
414
415    /**
416     * See {@link Activity#onRestart()}
417     */
418    protected void onRestart() {
419    }
420
421    /**
422     * See {@link Activity#onResume()}
423     */
424    protected void onResume() {
425    }
426
427    /**
428     * See {@link Activity#onPostResume()}
429     */
430    protected void onPostResume() {
431    }
432
433    /**
434     * See {@link Activity#onPause()}
435     */
436    protected void onPause() {
437    }
438
439    /**
440     * See {@link Activity#onStop()}
441     */
442    protected void onStop() {
443    }
444
445    /**
446     * See {@link Activity#onDestroy()}
447     */
448    protected void onDestroy() {
449    }
450
451    /**
452     * See {@link Activity#onRestoreInstanceState(Bundle)}
453     */
454    protected void onRestoreInstanceState(Bundle savedInstanceState) {
455    }
456
457    /**
458     * See {@link Activity#onSaveInstanceState(Bundle)}
459     */
460    protected void onSaveInstanceState(Bundle outState) {
461    }
462
463    /**
464     * See {@link Activity#onBackPressed()}
465     */
466    protected void onBackPressed() {
467    }
468
469    /**
470     * See {@link Activity#onConfigurationChanged(Configuration)}
471     */
472    protected void onConfigurationChanged(Configuration newConfig) {
473    }
474
475    /**
476     * See {@link Activity#onNewIntent(Intent)}
477     */
478    protected void onNewIntent(Intent intent) {
479    }
480
481    /**
482     * See {@link Activity#onRequestPermissionsResult(int, String[], int[])}
483     */
484    public void onRequestPermissionsResult(int requestCode, String[] permissions,
485            int[] grantResults) {
486    }
487
488    /**
489     * See {@link Activity#onActivityResult(int, int, Intent)}
490     */
491    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
492    }
493
494    /**
495     * See {@link Activity#onRetainNonConfigurationInstance()}
496     */
497    public Object onRetainNonConfigurationInstance() {
498        return null;
499    }
500
501    // TODO: hook up panel menu if it's needed in any apps.
502    /**
503     * Currently always returns false.
504     * See {@link Activity#onCreatePanelMenu(int, Menu)}
505     */
506    public boolean onCreatePanelMenu(int featureId, Menu menu) {
507        return false; // default menu will not be displayed.
508    }
509
510    /**
511     * See {@link Activity#onCreateView(View, String, Context, AttributeSet)}
512     */
513    public View onCreateView(View parent, String name, Context context, AttributeSet attrs) {
514        // CarFragmentActivity can override this to dispatch onCreateView to fragments
515        return null;
516    }
517
518    /**
519     * See {@link Activity#onLowMemory()}
520     */
521    public void onLowMemory() {
522    }
523
524    private void assertArgsLength(int length, Object... args) {
525        if (args == null || args.length != length) {
526            throw new IllegalArgumentException(
527                    String.format("Wrong number of parameters. Expected: %d Actual: %d",
528                            length, args == null ? 0 : args.length));
529        }
530    }
531
532    private static int[] convertArray(Integer[] array) {
533        int[] results = new int[array.length];
534        for(int i = 0; i < results.length; i++) {
535            results[i] = array[i].intValue();
536        }
537        return results;
538    }
539}
540