1 /*
2  * Copyright 2020 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 
17 package com.android.car.calendar;
18 
19 import android.content.ContentResolver;
20 import android.content.pm.PackageManager;
21 import android.os.Bundle;
22 import android.os.StrictMode;
23 import android.telephony.TelephonyManager;
24 import android.util.Log;
25 
26 import androidx.annotation.NonNull;
27 import androidx.annotation.Nullable;
28 import androidx.annotation.VisibleForTesting;
29 import androidx.fragment.app.FragmentActivity;
30 import androidx.lifecycle.ViewModel;
31 import androidx.lifecycle.ViewModelProvider;
32 
33 import com.android.car.calendar.common.CalendarFormatter;
34 import com.android.car.calendar.common.Dialer;
35 import com.android.car.calendar.common.Navigator;
36 import com.android.car.ui.core.CarUi;
37 import com.android.car.ui.toolbar.ToolbarController;
38 
39 import com.google.common.collect.HashMultimap;
40 import com.google.common.collect.Multimap;
41 
42 import java.time.Clock;
43 import java.util.Collection;
44 import java.util.Locale;
45 
46 /** The main Activity for the Car Calendar App. */
47 public class CarCalendarActivity extends FragmentActivity {
48     private static final String TAG = "CarCalendarActivity";
49     private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
50 
51     private final Multimap<String, Runnable> mPermissionToCallbacks = HashMultimap.create();
52 
53     // Allows tests to replace certain dependencies.
54     @VisibleForTesting Dependencies mDependencies;
55 
56     @Override
onCreate(@ullable Bundle savedInstanceState)57     protected void onCreate(@Nullable Bundle savedInstanceState) {
58         super.onCreate(savedInstanceState);
59         maybeEnableStrictMode();
60 
61         ToolbarController toolbar = CarUi.requireToolbar(this);
62         toolbar.setTitle(R.string.app_name);
63 
64         // Tests can set fake dependencies before onCreate.
65         if (mDependencies == null) {
66             mDependencies = new Dependencies(
67                     Locale.getDefault(),
68                     Clock.systemDefaultZone(),
69                     getContentResolver(),
70                     getSystemService(TelephonyManager.class));
71         }
72 
73         CarCalendarViewModel carCalendarViewModel =
74                 new ViewModelProvider(
75                                 this,
76                                 new CarCalendarViewModelFactory(
77                                         mDependencies.mResolver,
78                                         mDependencies.mLocale,
79                                         mDependencies.mTelephonyManager,
80                                         mDependencies.mClock))
81                         .get(CarCalendarViewModel.class);
82 
83         CarCalendarView carCalendarView =
84                 new CarCalendarView(
85                         this,
86                         carCalendarViewModel,
87                         new Navigator(this),
88                         new Dialer(this),
89                         new CalendarFormatter(
90                                 this.getApplicationContext(),
91                                 mDependencies.mLocale,
92                                 mDependencies.mClock));
93 
94         carCalendarView.show();
95     }
96 
maybeEnableStrictMode()97     private void maybeEnableStrictMode() {
98         if (DEBUG) {
99             Log.i(TAG, "Enabling strict mode");
100             StrictMode.setThreadPolicy(
101                     new StrictMode.ThreadPolicy.Builder()
102                             .detectAll()
103                             .penaltyLog()
104                             .penaltyDeath()
105                             .build());
106             StrictMode.setVmPolicy(
107                     new StrictMode.VmPolicy.Builder()
108                             .detectAll()
109                             .penaltyLog()
110                             .penaltyDeath()
111                             .build());
112         }
113     }
114 
115     /**
116      * Calls the given runnable only if the required permission is granted.
117      *
118      * <p>If the permission is already granted then the runnable is called immediately. Otherwise
119      * the runnable is retained and the permission is requested. If the permission is granted the
120      * runnable will be called otherwise it will be discarded.
121      */
runWithPermission(String permission, Runnable runnable)122     void runWithPermission(String permission, Runnable runnable) {
123         if (checkSelfPermission(permission) == PackageManager.PERMISSION_GRANTED) {
124             // Run immediately if we already have permission.
125             if (DEBUG) Log.d(TAG, "Running with " + permission);
126             runnable.run();
127         } else {
128             // Keep the runnable until the permission is granted.
129             if (DEBUG) Log.d(TAG, "Waiting for " + permission);
130             mPermissionToCallbacks.put(permission, runnable);
131             requestPermissions(new String[] {permission}, /* requestCode= */ 0);
132         }
133     }
134 
135     @Override
onRequestPermissionsResult( int requestCode, String[] permissions, int[] grantResults)136     public void onRequestPermissionsResult(
137             int requestCode, String[] permissions, int[] grantResults) {
138         super.onRequestPermissionsResult(requestCode, permissions, grantResults);
139         for (int i = 0; i < permissions.length; i++) {
140             String permission = permissions[i];
141             int grantResult = grantResults[i];
142             Collection<Runnable> callbacks = mPermissionToCallbacks.removeAll(permission);
143             if (grantResult == PackageManager.PERMISSION_GRANTED) {
144                 Log.e(TAG, "Permission " + permission + " granted");
145                 callbacks.forEach(Runnable::run);
146             } else {
147                 // TODO(jdp) Also allow a denied runnable.
148                 Log.e(TAG, "Permission " + permission + " not granted");
149             }
150         }
151     }
152 
153     private static class CarCalendarViewModelFactory implements ViewModelProvider.Factory {
154         private final ContentResolver mResolver;
155         private final TelephonyManager mTelephonyManager;
156         private final Locale mLocale;
157         private final Clock mClock;
158 
CarCalendarViewModelFactory( ContentResolver resolver, Locale locale, TelephonyManager telephonyManager, Clock clock)159         CarCalendarViewModelFactory(
160                 ContentResolver resolver,
161                 Locale locale,
162                 TelephonyManager telephonyManager,
163                 Clock clock) {
164             mResolver = resolver;
165             mLocale = locale;
166             mTelephonyManager = telephonyManager;
167             mClock = clock;
168         }
169 
170         @SuppressWarnings("unchecked")
171         @NonNull
172         @Override
create(@onNull Class<T> aClass)173         public <T extends ViewModel> T create(@NonNull Class<T> aClass) {
174             return (T) new CarCalendarViewModel(mResolver, mLocale, mTelephonyManager, mClock);
175         }
176     }
177 
178     static class Dependencies {
179         private final Locale mLocale;
180         private final Clock mClock;
181         private final ContentResolver mResolver;
182         private final TelephonyManager mTelephonyManager;
183 
Dependencies( Locale locale, Clock clock, ContentResolver resolver, TelephonyManager telephonyManager)184         Dependencies(
185                 Locale locale,
186                 Clock clock,
187                 ContentResolver resolver,
188                 TelephonyManager telephonyManager) {
189             mLocale = locale;
190             mClock = clock;
191             mResolver = resolver;
192             mTelephonyManager = telephonyManager;
193         }
194     }
195 }
196