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