LocationBasedCountryDetector.java revision a58a8751b4c2ce457f0082a0baaee61312d56195
1a58a8751b4c2ce457f0082a0baaee61312d56195Bai Tao/*
2a58a8751b4c2ce457f0082a0baaee61312d56195Bai Tao * Copyright (C) 2010 The Android Open Source Project
3a58a8751b4c2ce457f0082a0baaee61312d56195Bai Tao *
4a58a8751b4c2ce457f0082a0baaee61312d56195Bai Tao * Licensed under the Apache License, Version 2.0 (the "License");
5a58a8751b4c2ce457f0082a0baaee61312d56195Bai Tao * you may not use this file except in compliance with the License.
6a58a8751b4c2ce457f0082a0baaee61312d56195Bai Tao * You may obtain a copy of the License at
7a58a8751b4c2ce457f0082a0baaee61312d56195Bai Tao *
8a58a8751b4c2ce457f0082a0baaee61312d56195Bai Tao *      http://www.apache.org/licenses/LICENSE-2.0
9a58a8751b4c2ce457f0082a0baaee61312d56195Bai Tao *
10a58a8751b4c2ce457f0082a0baaee61312d56195Bai Tao * Unless required by applicable law or agreed to in writing, software
11a58a8751b4c2ce457f0082a0baaee61312d56195Bai Tao * distributed under the License is distributed on an "AS IS" BASIS,
12a58a8751b4c2ce457f0082a0baaee61312d56195Bai Tao * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13a58a8751b4c2ce457f0082a0baaee61312d56195Bai Tao * See the License for the specific language governing permissions and
14a58a8751b4c2ce457f0082a0baaee61312d56195Bai Tao * limitations under the License
15a58a8751b4c2ce457f0082a0baaee61312d56195Bai Tao */
16a58a8751b4c2ce457f0082a0baaee61312d56195Bai Tao
17a58a8751b4c2ce457f0082a0baaee61312d56195Bai Taopackage com.android.server.location;
18a58a8751b4c2ce457f0082a0baaee61312d56195Bai Tao
19a58a8751b4c2ce457f0082a0baaee61312d56195Bai Taoimport java.io.IOException;
20a58a8751b4c2ce457f0082a0baaee61312d56195Bai Taoimport java.util.ArrayList;
21a58a8751b4c2ce457f0082a0baaee61312d56195Bai Taoimport java.util.List;
22a58a8751b4c2ce457f0082a0baaee61312d56195Bai Taoimport java.util.Timer;
23a58a8751b4c2ce457f0082a0baaee61312d56195Bai Taoimport java.util.TimerTask;
24a58a8751b4c2ce457f0082a0baaee61312d56195Bai Tao
25a58a8751b4c2ce457f0082a0baaee61312d56195Bai Taoimport android.content.Context;
26a58a8751b4c2ce457f0082a0baaee61312d56195Bai Taoimport android.location.Address;
27a58a8751b4c2ce457f0082a0baaee61312d56195Bai Taoimport android.location.Country;
28a58a8751b4c2ce457f0082a0baaee61312d56195Bai Taoimport android.location.Geocoder;
29a58a8751b4c2ce457f0082a0baaee61312d56195Bai Taoimport android.location.Location;
30a58a8751b4c2ce457f0082a0baaee61312d56195Bai Taoimport android.location.LocationListener;
31a58a8751b4c2ce457f0082a0baaee61312d56195Bai Taoimport android.location.LocationManager;
32a58a8751b4c2ce457f0082a0baaee61312d56195Bai Taoimport android.os.Bundle;
33a58a8751b4c2ce457f0082a0baaee61312d56195Bai Taoimport android.util.Slog;
34a58a8751b4c2ce457f0082a0baaee61312d56195Bai Tao
35a58a8751b4c2ce457f0082a0baaee61312d56195Bai Tao/**
36a58a8751b4c2ce457f0082a0baaee61312d56195Bai Tao * This class detects which country the user currently is in through the enabled
37a58a8751b4c2ce457f0082a0baaee61312d56195Bai Tao * location providers and the GeoCoder
38a58a8751b4c2ce457f0082a0baaee61312d56195Bai Tao * <p>
39a58a8751b4c2ce457f0082a0baaee61312d56195Bai Tao * Use {@link #detectCountry} to start querying. If the location can not be
40a58a8751b4c2ce457f0082a0baaee61312d56195Bai Tao * resolved within the given time, the last known location will be used to get
41a58a8751b4c2ce457f0082a0baaee61312d56195Bai Tao * the user country through the GeoCoder. The IllegalStateException will be
42a58a8751b4c2ce457f0082a0baaee61312d56195Bai Tao * thrown if there is a ongoing query.
43a58a8751b4c2ce457f0082a0baaee61312d56195Bai Tao * <p>
44a58a8751b4c2ce457f0082a0baaee61312d56195Bai Tao * The current query can be stopped by {@link #stop()}
45a58a8751b4c2ce457f0082a0baaee61312d56195Bai Tao *
46a58a8751b4c2ce457f0082a0baaee61312d56195Bai Tao * @hide
47a58a8751b4c2ce457f0082a0baaee61312d56195Bai Tao */
48a58a8751b4c2ce457f0082a0baaee61312d56195Bai Taopublic class LocationBasedCountryDetector extends CountryDetectorBase {
49a58a8751b4c2ce457f0082a0baaee61312d56195Bai Tao    private final static String TAG = "LocationBasedCountryDetector";
50a58a8751b4c2ce457f0082a0baaee61312d56195Bai Tao    private final static long QUERY_LOCATION_TIMEOUT = 1000 * 60 * 5; // 5 mins
51a58a8751b4c2ce457f0082a0baaee61312d56195Bai Tao
52a58a8751b4c2ce457f0082a0baaee61312d56195Bai Tao    /**
53a58a8751b4c2ce457f0082a0baaee61312d56195Bai Tao     * Used for canceling location query
54a58a8751b4c2ce457f0082a0baaee61312d56195Bai Tao     */
55a58a8751b4c2ce457f0082a0baaee61312d56195Bai Tao    protected Timer mTimer;
56a58a8751b4c2ce457f0082a0baaee61312d56195Bai Tao
57a58a8751b4c2ce457f0082a0baaee61312d56195Bai Tao    /**
58a58a8751b4c2ce457f0082a0baaee61312d56195Bai Tao     * The thread to query the country from the GeoCoder.
59a58a8751b4c2ce457f0082a0baaee61312d56195Bai Tao     */
60a58a8751b4c2ce457f0082a0baaee61312d56195Bai Tao    protected Thread mQueryThread;
61a58a8751b4c2ce457f0082a0baaee61312d56195Bai Tao    protected List<LocationListener> mLocationListeners;
62a58a8751b4c2ce457f0082a0baaee61312d56195Bai Tao
63a58a8751b4c2ce457f0082a0baaee61312d56195Bai Tao    private LocationManager mLocationManager;
64a58a8751b4c2ce457f0082a0baaee61312d56195Bai Tao    private List<String> mEnabledProviders;
65a58a8751b4c2ce457f0082a0baaee61312d56195Bai Tao
66a58a8751b4c2ce457f0082a0baaee61312d56195Bai Tao    public LocationBasedCountryDetector(Context ctx) {
67a58a8751b4c2ce457f0082a0baaee61312d56195Bai Tao        super(ctx);
68a58a8751b4c2ce457f0082a0baaee61312d56195Bai Tao        mLocationManager = (LocationManager) ctx.getSystemService(Context.LOCATION_SERVICE);
69a58a8751b4c2ce457f0082a0baaee61312d56195Bai Tao    }
70a58a8751b4c2ce457f0082a0baaee61312d56195Bai Tao
71a58a8751b4c2ce457f0082a0baaee61312d56195Bai Tao    /**
72a58a8751b4c2ce457f0082a0baaee61312d56195Bai Tao     * @return the ISO 3166-1 two letters country code from the location
73a58a8751b4c2ce457f0082a0baaee61312d56195Bai Tao     */
74a58a8751b4c2ce457f0082a0baaee61312d56195Bai Tao    protected String getCountryFromLocation(Location location) {
75a58a8751b4c2ce457f0082a0baaee61312d56195Bai Tao        String country = null;
76a58a8751b4c2ce457f0082a0baaee61312d56195Bai Tao        Geocoder geoCoder = new Geocoder(mContext);
77a58a8751b4c2ce457f0082a0baaee61312d56195Bai Tao        try {
78a58a8751b4c2ce457f0082a0baaee61312d56195Bai Tao            List<Address> addresses = geoCoder.getFromLocation(
79a58a8751b4c2ce457f0082a0baaee61312d56195Bai Tao                    location.getLatitude(), location.getLongitude(), 1);
80a58a8751b4c2ce457f0082a0baaee61312d56195Bai Tao            if (addresses != null && addresses.size() > 0) {
81a58a8751b4c2ce457f0082a0baaee61312d56195Bai Tao                country = addresses.get(0).getCountryCode();
82a58a8751b4c2ce457f0082a0baaee61312d56195Bai Tao            }
83a58a8751b4c2ce457f0082a0baaee61312d56195Bai Tao        } catch (IOException e) {
84a58a8751b4c2ce457f0082a0baaee61312d56195Bai Tao            Slog.w(TAG, "Exception occurs when getting country from location");
85a58a8751b4c2ce457f0082a0baaee61312d56195Bai Tao        }
86a58a8751b4c2ce457f0082a0baaee61312d56195Bai Tao        return country;
87a58a8751b4c2ce457f0082a0baaee61312d56195Bai Tao    }
88a58a8751b4c2ce457f0082a0baaee61312d56195Bai Tao
89a58a8751b4c2ce457f0082a0baaee61312d56195Bai Tao    /**
90a58a8751b4c2ce457f0082a0baaee61312d56195Bai Tao     * Register the listeners with the location providers
91a58a8751b4c2ce457f0082a0baaee61312d56195Bai Tao     */
92a58a8751b4c2ce457f0082a0baaee61312d56195Bai Tao    protected void registerEnabledProviders(List<LocationListener> listeners) {
93a58a8751b4c2ce457f0082a0baaee61312d56195Bai Tao        int total = listeners.size();
94a58a8751b4c2ce457f0082a0baaee61312d56195Bai Tao        for (int i = 0; i< total; i++) {
95a58a8751b4c2ce457f0082a0baaee61312d56195Bai Tao            mLocationManager.requestLocationUpdates(
96a58a8751b4c2ce457f0082a0baaee61312d56195Bai Tao                    mEnabledProviders.get(i), 0, 0, listeners.get(i));
97a58a8751b4c2ce457f0082a0baaee61312d56195Bai Tao        }
98a58a8751b4c2ce457f0082a0baaee61312d56195Bai Tao    }
99a58a8751b4c2ce457f0082a0baaee61312d56195Bai Tao
100a58a8751b4c2ce457f0082a0baaee61312d56195Bai Tao    /**
101a58a8751b4c2ce457f0082a0baaee61312d56195Bai Tao     * Unregister the listeners with the location providers
102a58a8751b4c2ce457f0082a0baaee61312d56195Bai Tao     */
103a58a8751b4c2ce457f0082a0baaee61312d56195Bai Tao    protected void unregisterProviders(List<LocationListener> listeners) {
104a58a8751b4c2ce457f0082a0baaee61312d56195Bai Tao        for (LocationListener listener : listeners) {
105a58a8751b4c2ce457f0082a0baaee61312d56195Bai Tao            mLocationManager.removeUpdates(listener);
106a58a8751b4c2ce457f0082a0baaee61312d56195Bai Tao        }
107a58a8751b4c2ce457f0082a0baaee61312d56195Bai Tao    }
108a58a8751b4c2ce457f0082a0baaee61312d56195Bai Tao
109a58a8751b4c2ce457f0082a0baaee61312d56195Bai Tao    /**
110a58a8751b4c2ce457f0082a0baaee61312d56195Bai Tao     * @return the last known location from all providers
111a58a8751b4c2ce457f0082a0baaee61312d56195Bai Tao     */
112a58a8751b4c2ce457f0082a0baaee61312d56195Bai Tao    protected Location getLastKnownLocation() {
113a58a8751b4c2ce457f0082a0baaee61312d56195Bai Tao        List<String> providers = mLocationManager.getAllProviders();
114a58a8751b4c2ce457f0082a0baaee61312d56195Bai Tao        Location bestLocation = null;
115a58a8751b4c2ce457f0082a0baaee61312d56195Bai Tao        for (String provider : providers) {
116a58a8751b4c2ce457f0082a0baaee61312d56195Bai Tao            Location lastKnownLocation = mLocationManager.getLastKnownLocation(provider);
117a58a8751b4c2ce457f0082a0baaee61312d56195Bai Tao            if (lastKnownLocation != null) {
118a58a8751b4c2ce457f0082a0baaee61312d56195Bai Tao                if (bestLocation == null || bestLocation.getTime() < lastKnownLocation.getTime()) {
119a58a8751b4c2ce457f0082a0baaee61312d56195Bai Tao                    bestLocation = lastKnownLocation;
120a58a8751b4c2ce457f0082a0baaee61312d56195Bai Tao                }
121a58a8751b4c2ce457f0082a0baaee61312d56195Bai Tao            }
122a58a8751b4c2ce457f0082a0baaee61312d56195Bai Tao        }
123a58a8751b4c2ce457f0082a0baaee61312d56195Bai Tao        return bestLocation;
124a58a8751b4c2ce457f0082a0baaee61312d56195Bai Tao    }
125a58a8751b4c2ce457f0082a0baaee61312d56195Bai Tao
126a58a8751b4c2ce457f0082a0baaee61312d56195Bai Tao    /**
127a58a8751b4c2ce457f0082a0baaee61312d56195Bai Tao     * @return the timeout for querying the location.
128a58a8751b4c2ce457f0082a0baaee61312d56195Bai Tao     */
129a58a8751b4c2ce457f0082a0baaee61312d56195Bai Tao    protected long getQueryLocationTimeout() {
130a58a8751b4c2ce457f0082a0baaee61312d56195Bai Tao        return QUERY_LOCATION_TIMEOUT;
131a58a8751b4c2ce457f0082a0baaee61312d56195Bai Tao    }
132a58a8751b4c2ce457f0082a0baaee61312d56195Bai Tao
133a58a8751b4c2ce457f0082a0baaee61312d56195Bai Tao    /**
134a58a8751b4c2ce457f0082a0baaee61312d56195Bai Tao     * @return the total number of enabled location providers
135a58a8751b4c2ce457f0082a0baaee61312d56195Bai Tao     */
136a58a8751b4c2ce457f0082a0baaee61312d56195Bai Tao    protected int getTotalEnabledProviders() {
137a58a8751b4c2ce457f0082a0baaee61312d56195Bai Tao        if (mEnabledProviders == null) {
138a58a8751b4c2ce457f0082a0baaee61312d56195Bai Tao            mEnabledProviders = mLocationManager.getProviders(true);
139a58a8751b4c2ce457f0082a0baaee61312d56195Bai Tao        }
140a58a8751b4c2ce457f0082a0baaee61312d56195Bai Tao        return mEnabledProviders.size();
141a58a8751b4c2ce457f0082a0baaee61312d56195Bai Tao    }
142a58a8751b4c2ce457f0082a0baaee61312d56195Bai Tao
143a58a8751b4c2ce457f0082a0baaee61312d56195Bai Tao    /**
144a58a8751b4c2ce457f0082a0baaee61312d56195Bai Tao     * Start detecting the country.
145a58a8751b4c2ce457f0082a0baaee61312d56195Bai Tao     * <p>
146a58a8751b4c2ce457f0082a0baaee61312d56195Bai Tao     * Queries the location from all location providers, then starts a thread to query the
147a58a8751b4c2ce457f0082a0baaee61312d56195Bai Tao     * country from GeoCoder.
148a58a8751b4c2ce457f0082a0baaee61312d56195Bai Tao     */
149a58a8751b4c2ce457f0082a0baaee61312d56195Bai Tao    @Override
150a58a8751b4c2ce457f0082a0baaee61312d56195Bai Tao    public synchronized Country detectCountry() {
151a58a8751b4c2ce457f0082a0baaee61312d56195Bai Tao        if (mLocationListeners  != null) {
152a58a8751b4c2ce457f0082a0baaee61312d56195Bai Tao            throw new IllegalStateException();
153a58a8751b4c2ce457f0082a0baaee61312d56195Bai Tao        }
154a58a8751b4c2ce457f0082a0baaee61312d56195Bai Tao        // Request the location from all enabled providers.
155a58a8751b4c2ce457f0082a0baaee61312d56195Bai Tao        int totalProviders = getTotalEnabledProviders();
156a58a8751b4c2ce457f0082a0baaee61312d56195Bai Tao        if (totalProviders > 0) {
157a58a8751b4c2ce457f0082a0baaee61312d56195Bai Tao            mLocationListeners = new ArrayList<LocationListener>(totalProviders);
158a58a8751b4c2ce457f0082a0baaee61312d56195Bai Tao            for (int i = 0; i < totalProviders; i++) {
159a58a8751b4c2ce457f0082a0baaee61312d56195Bai Tao                LocationListener listener = new LocationListener () {
160a58a8751b4c2ce457f0082a0baaee61312d56195Bai Tao                    public void onLocationChanged(Location location) {
161a58a8751b4c2ce457f0082a0baaee61312d56195Bai Tao                        if (location != null) {
162a58a8751b4c2ce457f0082a0baaee61312d56195Bai Tao                            LocationBasedCountryDetector.this.stop();
163a58a8751b4c2ce457f0082a0baaee61312d56195Bai Tao                            queryCountryCode(location);
164a58a8751b4c2ce457f0082a0baaee61312d56195Bai Tao                        }
165a58a8751b4c2ce457f0082a0baaee61312d56195Bai Tao                    }
166a58a8751b4c2ce457f0082a0baaee61312d56195Bai Tao                    public void onProviderDisabled(String provider) {
167a58a8751b4c2ce457f0082a0baaee61312d56195Bai Tao                    }
168a58a8751b4c2ce457f0082a0baaee61312d56195Bai Tao                    public void onProviderEnabled(String provider) {
169a58a8751b4c2ce457f0082a0baaee61312d56195Bai Tao                    }
170a58a8751b4c2ce457f0082a0baaee61312d56195Bai Tao                    public void onStatusChanged(String provider, int status, Bundle extras) {
171a58a8751b4c2ce457f0082a0baaee61312d56195Bai Tao                    }
172a58a8751b4c2ce457f0082a0baaee61312d56195Bai Tao                };
173a58a8751b4c2ce457f0082a0baaee61312d56195Bai Tao                mLocationListeners.add(listener);
174a58a8751b4c2ce457f0082a0baaee61312d56195Bai Tao            }
175a58a8751b4c2ce457f0082a0baaee61312d56195Bai Tao            registerEnabledProviders(mLocationListeners);
176a58a8751b4c2ce457f0082a0baaee61312d56195Bai Tao            mTimer = new Timer();
177a58a8751b4c2ce457f0082a0baaee61312d56195Bai Tao            mTimer.schedule(new TimerTask() {
178a58a8751b4c2ce457f0082a0baaee61312d56195Bai Tao                @Override
179a58a8751b4c2ce457f0082a0baaee61312d56195Bai Tao                public void run() {
180a58a8751b4c2ce457f0082a0baaee61312d56195Bai Tao                    mTimer = null;
181a58a8751b4c2ce457f0082a0baaee61312d56195Bai Tao                    LocationBasedCountryDetector.this.stop();
182a58a8751b4c2ce457f0082a0baaee61312d56195Bai Tao                    // Looks like no provider could provide the location, let's try the last
183a58a8751b4c2ce457f0082a0baaee61312d56195Bai Tao                    // known location.
184a58a8751b4c2ce457f0082a0baaee61312d56195Bai Tao                    queryCountryCode(getLastKnownLocation());
185a58a8751b4c2ce457f0082a0baaee61312d56195Bai Tao                }
186a58a8751b4c2ce457f0082a0baaee61312d56195Bai Tao            }, getQueryLocationTimeout());
187a58a8751b4c2ce457f0082a0baaee61312d56195Bai Tao        } else {
188a58a8751b4c2ce457f0082a0baaee61312d56195Bai Tao            // There is no provider enabled.
189a58a8751b4c2ce457f0082a0baaee61312d56195Bai Tao            queryCountryCode(getLastKnownLocation());
190a58a8751b4c2ce457f0082a0baaee61312d56195Bai Tao        }
191a58a8751b4c2ce457f0082a0baaee61312d56195Bai Tao        return mDetectedCountry;
192a58a8751b4c2ce457f0082a0baaee61312d56195Bai Tao    }
193a58a8751b4c2ce457f0082a0baaee61312d56195Bai Tao
194a58a8751b4c2ce457f0082a0baaee61312d56195Bai Tao    /**
195a58a8751b4c2ce457f0082a0baaee61312d56195Bai Tao     * Stop the current query without notifying the listener.
196a58a8751b4c2ce457f0082a0baaee61312d56195Bai Tao     */
197a58a8751b4c2ce457f0082a0baaee61312d56195Bai Tao    @Override
198a58a8751b4c2ce457f0082a0baaee61312d56195Bai Tao    public synchronized void stop() {
199a58a8751b4c2ce457f0082a0baaee61312d56195Bai Tao        if (mLocationListeners != null) {
200a58a8751b4c2ce457f0082a0baaee61312d56195Bai Tao            unregisterProviders(mLocationListeners);
201a58a8751b4c2ce457f0082a0baaee61312d56195Bai Tao            mLocationListeners = null;
202a58a8751b4c2ce457f0082a0baaee61312d56195Bai Tao        }
203a58a8751b4c2ce457f0082a0baaee61312d56195Bai Tao        if (mTimer != null) {
204a58a8751b4c2ce457f0082a0baaee61312d56195Bai Tao            mTimer.cancel();
205a58a8751b4c2ce457f0082a0baaee61312d56195Bai Tao            mTimer = null;
206a58a8751b4c2ce457f0082a0baaee61312d56195Bai Tao        }
207a58a8751b4c2ce457f0082a0baaee61312d56195Bai Tao    }
208a58a8751b4c2ce457f0082a0baaee61312d56195Bai Tao
209a58a8751b4c2ce457f0082a0baaee61312d56195Bai Tao    /**
210a58a8751b4c2ce457f0082a0baaee61312d56195Bai Tao     * Start a new thread to query the country from Geocoder.
211a58a8751b4c2ce457f0082a0baaee61312d56195Bai Tao     */
212a58a8751b4c2ce457f0082a0baaee61312d56195Bai Tao    private synchronized void queryCountryCode(final Location location) {
213a58a8751b4c2ce457f0082a0baaee61312d56195Bai Tao        if (location == null) {
214a58a8751b4c2ce457f0082a0baaee61312d56195Bai Tao            notifyListener(null);
215a58a8751b4c2ce457f0082a0baaee61312d56195Bai Tao            return;
216a58a8751b4c2ce457f0082a0baaee61312d56195Bai Tao        }
217a58a8751b4c2ce457f0082a0baaee61312d56195Bai Tao        if (mQueryThread != null) return;
218a58a8751b4c2ce457f0082a0baaee61312d56195Bai Tao        mQueryThread = new Thread(new Runnable() {
219a58a8751b4c2ce457f0082a0baaee61312d56195Bai Tao            public void run() {
220a58a8751b4c2ce457f0082a0baaee61312d56195Bai Tao                String countryIso = null;
221a58a8751b4c2ce457f0082a0baaee61312d56195Bai Tao                if (location != null) {
222a58a8751b4c2ce457f0082a0baaee61312d56195Bai Tao                    countryIso = getCountryFromLocation(location);
223a58a8751b4c2ce457f0082a0baaee61312d56195Bai Tao                }
224a58a8751b4c2ce457f0082a0baaee61312d56195Bai Tao                if (countryIso != null) {
225a58a8751b4c2ce457f0082a0baaee61312d56195Bai Tao                    mDetectedCountry = new Country(countryIso, Country.COUNTRY_SOURCE_LOCATION);
226a58a8751b4c2ce457f0082a0baaee61312d56195Bai Tao                } else {
227a58a8751b4c2ce457f0082a0baaee61312d56195Bai Tao                    mDetectedCountry = null;
228a58a8751b4c2ce457f0082a0baaee61312d56195Bai Tao                }
229a58a8751b4c2ce457f0082a0baaee61312d56195Bai Tao                notifyListener(mDetectedCountry);
230a58a8751b4c2ce457f0082a0baaee61312d56195Bai Tao                mQueryThread = null;
231a58a8751b4c2ce457f0082a0baaee61312d56195Bai Tao            }
232a58a8751b4c2ce457f0082a0baaee61312d56195Bai Tao        });
233a58a8751b4c2ce457f0082a0baaee61312d56195Bai Tao        mQueryThread.start();
234a58a8751b4c2ce457f0082a0baaee61312d56195Bai Tao    }
235a58a8751b4c2ce457f0082a0baaee61312d56195Bai Tao}
236