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