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