1/*
2 * Copyright (C) 2017 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
5 * except in compliance with the License. You may obtain a copy of the License at
6 *
7 *      http://www.apache.org/licenses/LICENSE-2.0
8 *
9 * Unless required by applicable law or agreed to in writing, software distributed under the
10 * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
11 * KIND, either express or implied. See the License for the specific language governing
12 * permissions and limitations under the License.
13 */
14package com.android.settings.location;
15
16import android.app.ActivityManager;
17import android.Manifest.permission;
18import android.content.BroadcastReceiver;
19import android.content.Context;
20import android.content.Intent;
21import android.content.IntentFilter;
22import android.location.LocationManager;
23import android.os.UserHandle;
24import android.os.UserManager;
25import android.provider.Settings;
26import android.support.annotation.VisibleForTesting;
27import android.util.Log;
28
29import com.android.settings.Utils;
30import com.android.settingslib.RestrictedLockUtils;
31import com.android.settingslib.core.lifecycle.Lifecycle;
32import com.android.settingslib.core.lifecycle.LifecycleObserver;
33import com.android.settingslib.core.lifecycle.events.OnPause;
34import com.android.settingslib.core.lifecycle.events.OnResume;
35
36import static com.android.settingslib.Utils.updateLocationMode;
37import static com.android.settingslib.Utils.updateLocationEnabled;
38import static com.android.settingslib.RestrictedLockUtils.checkIfRestrictionEnforced;
39
40
41/**
42 * A class that listens to location settings change and modifies location settings
43 * settings.
44 */
45public class LocationEnabler implements LifecycleObserver, OnResume, OnPause {
46
47    private static final String TAG = "LocationEnabler";
48    @VisibleForTesting
49    static final IntentFilter INTENT_FILTER_LOCATION_MODE_CHANGED =
50            new IntentFilter(LocationManager.MODE_CHANGED_ACTION);
51
52    private final Context mContext;
53    private final UserManager mUserManager;
54    private final LocationModeChangeListener mListener;
55
56    @VisibleForTesting
57    BroadcastReceiver mReceiver;
58
59    public interface LocationModeChangeListener {
60        /** Called when location mode has changed. */
61        void onLocationModeChanged(int mode, boolean restricted);
62    }
63
64    public LocationEnabler(Context context, LocationModeChangeListener listener,
65            Lifecycle lifecycle) {
66        mContext = context;
67        mListener = listener;
68        mUserManager = (UserManager) context.getSystemService(Context.USER_SERVICE);
69        if (lifecycle != null) {
70            lifecycle.addObserver(this);
71        }
72    }
73
74    @Override
75    public void onResume() {
76        if (mReceiver == null) {
77            mReceiver = new BroadcastReceiver() {
78                @Override
79                public void onReceive(Context context, Intent intent) {
80                    if (Log.isLoggable(TAG, Log.DEBUG)) {
81                        Log.d(TAG, "Received location mode change intent: " + intent);
82                    }
83                    refreshLocationMode();
84                }
85            };
86        }
87        mContext.registerReceiver(mReceiver, INTENT_FILTER_LOCATION_MODE_CHANGED);
88        refreshLocationMode();
89    }
90
91    @Override
92    public void onPause() {
93        try {
94            mContext.unregisterReceiver(mReceiver);
95        } catch (RuntimeException e) {
96            // Ignore exceptions caused by race condition
97        }
98    }
99
100    void refreshLocationMode() {
101        final int mode = Settings.Secure.getInt(mContext.getContentResolver(),
102                Settings.Secure.LOCATION_MODE, Settings.Secure.LOCATION_MODE_OFF);
103        if (Log.isLoggable(TAG, Log.INFO)) {
104            Log.i(TAG, "Location mode has been changed");
105        }
106        if (mListener != null) {
107            mListener.onLocationModeChanged(mode, isRestricted());
108        }
109    }
110
111    void setLocationEnabled(boolean enabled) {
112        final int currentMode = Settings.Secure.getInt(mContext.getContentResolver(),
113            Settings.Secure.LOCATION_MODE, Settings.Secure.LOCATION_MODE_OFF);
114
115        if (isRestricted()) {
116            // Location toggling disabled by user restriction. Read the current location mode to
117            // update the location master switch.
118            if (Log.isLoggable(TAG, Log.INFO)) {
119                Log.i(TAG, "Restricted user, not setting location mode");
120            }
121            if (mListener != null) {
122                mListener.onLocationModeChanged(currentMode, true);
123            }
124            return;
125        }
126        updateLocationEnabled(mContext, enabled, UserHandle.myUserId(),
127                Settings.Secure.LOCATION_CHANGER_SYSTEM_SETTINGS);
128        refreshLocationMode();
129    }
130
131    void setLocationMode(int mode) {
132        final int currentMode = Settings.Secure.getInt(mContext.getContentResolver(),
133                Settings.Secure.LOCATION_MODE, Settings.Secure.LOCATION_MODE_OFF);
134        if (isRestricted()) {
135            // Location toggling disabled by user restriction. Read the current location mode to
136            // update the location master switch.
137            if (Log.isLoggable(TAG, Log.INFO)) {
138                Log.i(TAG, "Restricted user, not setting location mode");
139            }
140            if (mListener != null) {
141                mListener.onLocationModeChanged(currentMode, true);
142            }
143            return;
144        }
145
146        updateLocationMode(mContext, currentMode, mode, ActivityManager.getCurrentUser(),
147                Settings.Secure.LOCATION_CHANGER_SYSTEM_SETTINGS);
148        refreshLocationMode();
149    }
150
151    boolean isEnabled(int mode) {
152        return mode != Settings.Secure.LOCATION_MODE_OFF && !isRestricted();
153    }
154
155    /**
156     * Checking if device policy has put a location access lock-down on the managed profile.
157     *
158     * @return true if device policy has put a location access lock-down on the managed profile
159     */
160    boolean isManagedProfileRestrictedByBase() {
161        final UserHandle managedProfile = Utils.getManagedProfile(mUserManager);
162        return managedProfile != null
163                && hasShareLocationRestriction(managedProfile.getIdentifier());
164    }
165
166    RestrictedLockUtils.EnforcedAdmin getShareLocationEnforcedAdmin(int userId) {
167        RestrictedLockUtils.EnforcedAdmin admin =  checkIfRestrictionEnforced(
168                mContext, UserManager.DISALLOW_SHARE_LOCATION, userId);
169
170        if (admin == null) {
171            admin = RestrictedLockUtils.checkIfRestrictionEnforced(
172                    mContext, UserManager.DISALLOW_CONFIG_LOCATION, userId);
173        }
174        return admin;
175    }
176
177    boolean hasShareLocationRestriction(int userId) {
178        return RestrictedLockUtils.hasBaseUserRestriction(
179                mContext, UserManager.DISALLOW_SHARE_LOCATION, userId);
180    }
181
182    private boolean isRestricted() {
183        return mUserManager.hasUserRestriction(UserManager.DISALLOW_SHARE_LOCATION);
184    }
185}
186