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 */
16package android.support.car.app.menu;
17
18import android.car.app.menu.CarUiEntry;
19import android.content.Context;
20import android.content.pm.ApplicationInfo;
21import android.content.pm.PackageManager;
22import android.graphics.Bitmap;
23import android.os.Bundle;
24import android.support.car.app.menu.compat.EmbeddedCarMenuCallbacksCompat;
25import android.support.car.app.menu.compat.EmbeddedSearchBoxEditListenerCompat;
26import android.view.View;
27import android.widget.EditText;
28import java.lang.reflect.InvocationTargetException;
29
30/**
31 * A {@link android.support.car.app.menu.CarUiController} that talks to embedded car ui provider.
32 * @hide
33 */
34public class EmbeddedCarUiController extends CarUiController {
35
36    private static final String TAG = "EmbeddedCarUiController";
37    // TODO: load the package name and class name from resources
38    private static final String UI_ENTRY_CLASS_NAME = ".CarUiEntry";
39    private static final String CAR_UI_PROVIDER_PKG = "android.car.ui.provider";
40    private static final String CAR_SERVICE_PKG = "com.android.car";
41
42    private CarUiEntry mCarUiEntry;
43    private EmbeddedCarMenuCallbacksCompat mCallback;
44
45    public EmbeddedCarUiController(CarDrawerActivity activity) {
46        super(activity);
47        try {
48            Context carUiContext = mActivity.getContext().createPackageContext(
49                    CAR_UI_PROVIDER_PKG,
50                    Context.CONTEXT_INCLUDE_CODE | Context.CONTEXT_IGNORE_SECURITY);
51
52            ClassLoader classLoader = carUiContext.getClassLoader();
53            Class<?> loadedClass = classLoader.loadClass(CAR_UI_PROVIDER_PKG + UI_ENTRY_CLASS_NAME);
54            mCarUiEntry = (CarUiEntry) loadedClass.getConstructor(Context.class, Context.class)
55                    .newInstance(carUiContext, mActivity.getContext());
56        } catch (PackageManager.NameNotFoundException | ClassNotFoundException e) {
57            throw new RuntimeException("Cannot find CarUiEntry from " + CAR_UI_PROVIDER_PKG + "/"
58                    + UI_ENTRY_CLASS_NAME, e);
59        } catch (IllegalAccessException | InvocationTargetException | InstantiationException
60                | NoSuchMethodException  e) {
61            throw new RuntimeException("Cannot cast CarUiEntry.", e);
62        }
63    }
64
65    @Override
66    public void validateCarUiPackage() {
67        try {
68            PackageManager packageManager = mActivity.getContext().getPackageManager();
69            int flag = packageManager.getApplicationInfo(CAR_UI_PROVIDER_PKG, 0).flags;
70            if ((flag & ApplicationInfo.FLAG_SYSTEM) == 0
71                    && (flag & ApplicationInfo.FLAG_UPDATED_SYSTEM_APP) == 0) {
72                throw new SecurityException("CarUiProvider is not a system app!");
73            }
74
75            // Do not change the order of the two packages as it need to be in sync with
76            // the error message.
77            int signatureMatchResult =
78                    packageManager.checkSignatures(CAR_SERVICE_PKG, CAR_UI_PROVIDER_PKG);
79            if (signatureMatchResult != PackageManager.SIGNATURE_MATCH) {
80                throw new SecurityException("CarUiProvider and CarService signature check" +
81                        " failed. " + getSignatureFailureMessage(signatureMatchResult));
82            }
83        } catch (PackageManager.NameNotFoundException e) {
84            throw new RuntimeException("Cannot find CarUiProvider" + CAR_UI_PROVIDER_PKG, e);
85        }
86    }
87
88    @Override
89    public int getFragmentContainerId() {
90        return mCarUiEntry.getFragmentContainerId();
91    }
92
93    @Override
94    public void setTitle(CharSequence title) {
95        mCarUiEntry.setTitle(title);
96    }
97
98    @Override
99    public void setScrimColor(int color) {
100        mCarUiEntry.setScrimColor(color);
101    }
102
103    @Override
104    public View getContentView() {
105        return mCarUiEntry.getContentView();
106    }
107
108    @Override
109    public void registerCarMenuCallbacks(final CarMenuCallbacks callbacks) {
110        mCallback = new EmbeddedCarMenuCallbacksCompat(mActivity, callbacks);
111        mCarUiEntry.setCarMenuCallbacks(mCallback);
112    }
113
114    @Override
115    public void restoreMenuButtonDrawable() {
116        mCarUiEntry.restoreMenuDrawable();
117    }
118
119    @Override
120    public void setMenuButtonBitmap(Bitmap bitmap) {
121        mCarUiEntry.setMenuButtonBitmap(bitmap);
122    }
123
124    @Override
125    public void setLightMode() {
126        mCarUiEntry.setLightMode();
127    }
128
129    @Override
130    public void setDarkMode() {
131        mCarUiEntry.setDarkMode();
132    }
133
134    @Override
135    public void setAutoLightDarkMode() {
136        mCarUiEntry.setAutoLightDarkMode();
137    }
138
139    @Override
140    public void setBackground(Bitmap bitmap) {
141        mCarUiEntry.setBackground(bitmap);
142    }
143
144    @Override
145    public void onRestoreInstanceState(Bundle savedState) {
146        mCarUiEntry.onRestoreInstanceState(savedState);
147    }
148
149    @Override
150    public void onSaveInstanceState(Bundle outState) {
151        mCarUiEntry.onSaveInstanceState(outState);
152    }
153
154    @Override
155    public void closeDrawer() {
156        mCarUiEntry.closeDrawer();
157    }
158
159    @Override
160    public void openDrawer() {
161        mCarUiEntry.openDrawer();
162    }
163
164    @Override
165    public void showMenu(String id, String title) {
166        mCarUiEntry.showMenu(id, title);
167    }
168
169    @Override
170    public void onStart() {
171        mCarUiEntry.onStart();
172    }
173
174    @Override
175    public void onResume() {
176        mCarUiEntry.onResume();
177    }
178
179    @Override
180    public void onPause() {
181        mCarUiEntry.onPause();
182    }
183
184    @Override
185    public void onStop() {
186        mCarUiEntry.onStop();
187    }
188
189    @Override
190    public void showSearchBox(View.OnClickListener listener) {
191        mCarUiEntry.showSearchBox(listener);
192    }
193
194    @Override
195    public void setSearchBoxColors(int backgroundColor, int searchLogocolor, int textColor, int hintTextColor) {
196        mCarUiEntry.setSearchBoxColors(backgroundColor, searchLogocolor, textColor, hintTextColor);
197    }
198
199    @Override
200    public void setSearchBoxEditListener(SearchBoxEditListener listener) {
201        mCarUiEntry.setSearchBoxEditListener(new EmbeddedSearchBoxEditListenerCompat(listener));
202    }
203
204    @Override
205    public CharSequence getText() {
206        return mCarUiEntry.getSearchBoxText();
207    }
208
209    @Override
210    public void stopInput() {
211        mCarUiEntry.stopInput();
212    }
213
214    @Override
215    public EditText startInput(String hint, View.OnClickListener listener) {
216        return mCarUiEntry.startInput(hint, listener);
217    }
218
219    @Override
220    public void setSearchBoxEndView(View view) {
221        mCarUiEntry.setSearchBoxEndView(view);
222    }
223
224    @Override
225    public void onChildChanged(String parentId, Bundle item) {
226        mCallback.onChildChanged(parentId, item);
227    }
228
229    @Override
230    public void onChildrenChanged(String parentId) {
231        mCallback.onChildrenChanged(parentId);
232    }
233
234    @Override
235    public void showToast(String msg, int duration) {
236        mCarUiEntry.showToast(msg, duration);
237    }
238
239    /**
240     * Return more informative error message from the PackageManager's signature check result.
241     */
242    private static final String getSignatureFailureMessage(int code) {
243        switch (code) {
244            case PackageManager.SIGNATURE_NEITHER_SIGNED:
245                return "Both CarService and CarUiProvider are not signed";
246            case PackageManager.SIGNATURE_FIRST_NOT_SIGNED:
247                return "CarService not signed";
248            case PackageManager.SIGNATURE_SECOND_NOT_SIGNED:
249                return "CarUiProvider not signed";
250            case PackageManager.SIGNATURE_NO_MATCH:
251                return "Signatures do not match";
252            case PackageManager.SIGNATURE_UNKNOWN_PACKAGE:
253                return "CarService not found";
254            default:
255                return "Unknown error code";
256        }
257    }
258}
259