1 /* 2 * Copyright (C) 2017 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.settings.development; 18 19 import static com.android.settings.development.DevelopmentOptionsActivityRequestCodes.REQUEST_MOCK_LOCATION_APP; 20 21 import android.Manifest; 22 import android.app.Activity; 23 import android.app.AppOpsManager; 24 import android.content.Context; 25 import android.content.Intent; 26 import android.content.pm.ApplicationInfo; 27 import android.content.pm.PackageManager; 28 import android.text.TextUtils; 29 30 import androidx.annotation.VisibleForTesting; 31 import androidx.preference.Preference; 32 33 import com.android.settings.R; 34 import com.android.settings.core.PreferenceControllerMixin; 35 import com.android.settingslib.development.DeveloperOptionsPreferenceController; 36 37 import java.util.List; 38 39 public class MockLocationAppPreferenceController extends DeveloperOptionsPreferenceController 40 implements PreferenceControllerMixin, OnActivityResultListener { 41 42 private static final String MOCK_LOCATION_APP_KEY = "mock_location_app"; 43 private static final int[] MOCK_LOCATION_APP_OPS = new int[]{AppOpsManager.OP_MOCK_LOCATION}; 44 45 private final DevelopmentSettingsDashboardFragment mFragment; 46 private final AppOpsManager mAppsOpsManager; 47 private final PackageManager mPackageManager; 48 MockLocationAppPreferenceController(Context context, DevelopmentSettingsDashboardFragment fragment)49 public MockLocationAppPreferenceController(Context context, 50 DevelopmentSettingsDashboardFragment fragment) { 51 super(context); 52 53 mFragment = fragment; 54 mAppsOpsManager = (AppOpsManager) context.getSystemService(Context.APP_OPS_SERVICE); 55 mPackageManager = context.getPackageManager(); 56 } 57 58 @Override getPreferenceKey()59 public String getPreferenceKey() { 60 return MOCK_LOCATION_APP_KEY; 61 } 62 63 @Override handlePreferenceTreeClick(Preference preference)64 public boolean handlePreferenceTreeClick(Preference preference) { 65 if (!TextUtils.equals(preference.getKey(), getPreferenceKey())) { 66 return false; 67 } 68 final Intent intent = new Intent(mContext, AppPicker.class); 69 intent.putExtra(AppPicker.EXTRA_REQUESTIING_PERMISSION, 70 Manifest.permission.ACCESS_MOCK_LOCATION); 71 mFragment.startActivityForResult(intent, REQUEST_MOCK_LOCATION_APP); 72 return true; 73 } 74 75 @Override updateState(Preference preference)76 public void updateState(Preference preference) { 77 updateMockLocation(); 78 } 79 80 @Override onActivityResult(int requestCode, int resultCode, Intent data)81 public boolean onActivityResult(int requestCode, int resultCode, Intent data) { 82 if (requestCode != REQUEST_MOCK_LOCATION_APP || resultCode != Activity.RESULT_OK) { 83 return false; 84 } 85 writeMockLocation(data.getAction()); 86 updateMockLocation(); 87 return true; 88 } 89 90 @Override onDeveloperOptionsDisabled()91 public void onDeveloperOptionsDisabled() { 92 super.onDeveloperOptionsDisabled(); 93 removeAllMockLocations(); 94 } 95 updateMockLocation()96 private void updateMockLocation() { 97 final String mockLocationApp = getCurrentMockLocationApp(); 98 99 if (!TextUtils.isEmpty(mockLocationApp)) { 100 mPreference.setSummary( 101 mContext.getResources().getString(R.string.mock_location_app_set, 102 getAppLabel(mockLocationApp))); 103 } else { 104 mPreference.setSummary( 105 mContext.getResources().getString(R.string.mock_location_app_not_set)); 106 } 107 } 108 writeMockLocation(String mockLocationAppName)109 private void writeMockLocation(String mockLocationAppName) { 110 removeAllMockLocations(); 111 // Enable the app op of the new mock location app if such. 112 if (!TextUtils.isEmpty(mockLocationAppName)) { 113 try { 114 final ApplicationInfo ai = mPackageManager.getApplicationInfo( 115 mockLocationAppName, PackageManager.MATCH_DISABLED_COMPONENTS); 116 mAppsOpsManager.setMode(AppOpsManager.OP_MOCK_LOCATION, ai.uid, 117 mockLocationAppName, AppOpsManager.MODE_ALLOWED); 118 } catch (PackageManager.NameNotFoundException e) { 119 /* ignore */ 120 } 121 } 122 } 123 getAppLabel(String mockLocationApp)124 private String getAppLabel(String mockLocationApp) { 125 try { 126 final ApplicationInfo ai = mPackageManager.getApplicationInfo( 127 mockLocationApp, PackageManager.MATCH_DISABLED_COMPONENTS); 128 final CharSequence appLabel = mPackageManager.getApplicationLabel(ai); 129 return appLabel != null ? appLabel.toString() : mockLocationApp; 130 } catch (PackageManager.NameNotFoundException e) { 131 return mockLocationApp; 132 } 133 } 134 removeAllMockLocations()135 private void removeAllMockLocations() { 136 // Disable the app op of the previous mock location app if such. 137 final List<AppOpsManager.PackageOps> packageOps = mAppsOpsManager.getPackagesForOps( 138 MOCK_LOCATION_APP_OPS); 139 if (packageOps == null) { 140 return; 141 } 142 // Should be one but in case we are in a bad state due to use of command line tools. 143 for (AppOpsManager.PackageOps packageOp : packageOps) { 144 if (packageOp.getOps().get(0).getMode() != AppOpsManager.MODE_ERRORED) { 145 removeMockLocationForApp(packageOp.getPackageName()); 146 } 147 } 148 } 149 removeMockLocationForApp(String appName)150 private void removeMockLocationForApp(String appName) { 151 try { 152 final ApplicationInfo ai = mPackageManager.getApplicationInfo( 153 appName, PackageManager.MATCH_DISABLED_COMPONENTS); 154 mAppsOpsManager.setMode(AppOpsManager.OP_MOCK_LOCATION, ai.uid, 155 appName, AppOpsManager.MODE_ERRORED); 156 } catch (PackageManager.NameNotFoundException e) { 157 /* ignore */ 158 } 159 } 160 161 @VisibleForTesting getCurrentMockLocationApp()162 String getCurrentMockLocationApp() { 163 final List<AppOpsManager.PackageOps> packageOps = mAppsOpsManager.getPackagesForOps( 164 MOCK_LOCATION_APP_OPS); 165 if (packageOps != null) { 166 for (AppOpsManager.PackageOps packageOp : packageOps) { 167 if (packageOp.getOps().get(0).getMode() == AppOpsManager.MODE_ALLOWED) { 168 return packageOps.get(0).getPackageName(); 169 } 170 } 171 } 172 return null; 173 } 174 } 175