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