Home | History | Annotate | Download | only in common
      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 package com.android.tv.common;
     17 
     18 import android.content.ContentResolver;
     19 import android.content.ContentValues;
     20 import android.content.Context;
     21 import android.content.SharedPreferences;
     22 import android.database.ContentObserver;
     23 import android.database.Cursor;
     24 import android.os.AsyncTask;
     25 import android.os.Bundle;
     26 import android.os.Handler;
     27 import android.support.annotation.GuardedBy;
     28 import android.support.annotation.IntDef;
     29 import android.support.annotation.MainThread;
     30 import android.util.Log;
     31 import com.android.tv.common.CommonPreferenceProvider.Preferences;
     32 import com.android.tv.common.util.CommonUtils;
     33 import java.lang.annotation.Retention;
     34 import java.lang.annotation.RetentionPolicy;
     35 import java.util.HashMap;
     36 import java.util.Map;
     37 
     38 /** A helper class for setting/getting common preferences across applications. */
     39 public class CommonPreferences {
     40     private static final String TAG = "CommonPreferences";
     41 
     42     private static final String PREFS_KEY_LAUNCH_SETUP = "launch_setup";
     43     private static final String PREFS_KEY_STORE_TS_STREAM = "store_ts_stream";
     44     private static final String PREFS_KEY_TRICKPLAY_SETTING = "trickplay_setting";
     45     private static final String PREFS_KEY_LAST_POSTAL_CODE = "last_postal_code";
     46 
     47     private static final Map<String, Class> sPref2TypeMapping = new HashMap<>();
     48 
     49     static {
     50         sPref2TypeMapping.put(PREFS_KEY_TRICKPLAY_SETTING, int.class);
     51         sPref2TypeMapping.put(PREFS_KEY_STORE_TS_STREAM, boolean.class);
     52         sPref2TypeMapping.put(PREFS_KEY_LAUNCH_SETUP, boolean.class);
     53         sPref2TypeMapping.put(PREFS_KEY_LAST_POSTAL_CODE, String.class);
     54     }
     55 
     56     private static final String SHARED_PREFS_NAME =
     57             CommonConstants.BASE_PACKAGE + ".common.preferences";
     58 
     59     @IntDef({TRICKPLAY_SETTING_NOT_SET, TRICKPLAY_SETTING_DISABLED, TRICKPLAY_SETTING_ENABLED})
     60     @Retention(RetentionPolicy.SOURCE)
     61     public @interface TrickplaySetting {}
     62 
     63     /** Trickplay setting is not changed by a user. Trickplay will be enabled in this case. */
     64     public static final int TRICKPLAY_SETTING_NOT_SET = -1;
     65 
     66     /** Trickplay setting is disabled. */
     67     public static final int TRICKPLAY_SETTING_DISABLED = 0;
     68 
     69     /** Trickplay setting is enabled. */
     70     public static final int TRICKPLAY_SETTING_ENABLED = 1;
     71 
     72     @GuardedBy("CommonPreferences.class")
     73     private static final Bundle sPreferenceValues = new Bundle();
     74 
     75     private static LoadPreferencesTask sLoadPreferencesTask;
     76     private static ContentObserver sContentObserver;
     77     private static CommonPreferencesChangedListener sPreferencesChangedListener = null;
     78 
     79     protected static boolean sInitialized;
     80 
     81     /** Listeners for CommonPreferences change. */
     82     public interface CommonPreferencesChangedListener {
     83         void onCommonPreferencesChanged();
     84     }
     85 
     86     /** Initializes the common preferences. */
     87     @MainThread
     88     public static void initialize(final Context context) {
     89         if (sInitialized) {
     90             return;
     91         }
     92         sInitialized = true;
     93         if (useContentProvider(context)) {
     94             loadPreferences(context);
     95             sContentObserver =
     96                     new ContentObserver(new Handler()) {
     97                         @Override
     98                         public void onChange(boolean selfChange) {
     99                             loadPreferences(context);
    100                         }
    101                     };
    102             context.getContentResolver()
    103                     .registerContentObserver(Preferences.CONTENT_URI, true, sContentObserver);
    104         } else {
    105             new AsyncTask<Void, Void, Void>() {
    106                 @Override
    107                 protected Void doInBackground(Void... params) {
    108                     getSharedPreferences(context);
    109                     return null;
    110                 }
    111             }.execute();
    112         }
    113     }
    114 
    115     /** Releases the resources. */
    116     public static synchronized void release(Context context) {
    117         if (useContentProvider(context) && sContentObserver != null) {
    118             context.getContentResolver().unregisterContentObserver(sContentObserver);
    119         }
    120         setCommonPreferencesChangedListener(null);
    121     }
    122 
    123     /** Sets the listener for CommonPreferences change. */
    124     public static void setCommonPreferencesChangedListener(
    125             CommonPreferencesChangedListener listener) {
    126         sPreferencesChangedListener = listener;
    127     }
    128 
    129     /**
    130      * Loads the preferences from database.
    131      *
    132      * <p>This preferences is used across processes, so the preferences should be loaded again when
    133      * the databases changes.
    134      */
    135     @MainThread
    136     public static void loadPreferences(Context context) {
    137         if (sLoadPreferencesTask != null
    138                 && sLoadPreferencesTask.getStatus() != AsyncTask.Status.FINISHED) {
    139             sLoadPreferencesTask.cancel(true);
    140         }
    141         sLoadPreferencesTask = new LoadPreferencesTask(context);
    142         sLoadPreferencesTask.execute();
    143     }
    144 
    145     private static boolean useContentProvider(Context context) {
    146         // If TIS is a part of LC, it should use ContentProvider to resolve multiple process access.
    147         return CommonUtils.isPackagedWithLiveChannels(context);
    148     }
    149 
    150     public static synchronized boolean shouldShowSetupActivity(Context context) {
    151         SoftPreconditions.checkState(sInitialized);
    152         if (useContentProvider(context)) {
    153             return sPreferenceValues.getBoolean(PREFS_KEY_LAUNCH_SETUP);
    154         } else {
    155             return getSharedPreferences(context).getBoolean(PREFS_KEY_LAUNCH_SETUP, false);
    156         }
    157     }
    158 
    159     public static synchronized void setShouldShowSetupActivity(Context context, boolean need) {
    160         if (useContentProvider(context)) {
    161             setPreference(context, PREFS_KEY_LAUNCH_SETUP, need);
    162         } else {
    163             getSharedPreferences(context).edit().putBoolean(PREFS_KEY_LAUNCH_SETUP, need).apply();
    164         }
    165     }
    166 
    167     public static synchronized @TrickplaySetting int getTrickplaySetting(Context context) {
    168         SoftPreconditions.checkState(sInitialized);
    169         if (useContentProvider(context)) {
    170             return sPreferenceValues.getInt(PREFS_KEY_TRICKPLAY_SETTING, TRICKPLAY_SETTING_NOT_SET);
    171         } else {
    172             return getSharedPreferences(context)
    173                     .getInt(PREFS_KEY_TRICKPLAY_SETTING, TRICKPLAY_SETTING_NOT_SET);
    174         }
    175     }
    176 
    177     public static synchronized void setTrickplaySetting(
    178             Context context, @TrickplaySetting int trickplaySetting) {
    179         SoftPreconditions.checkState(sInitialized);
    180         SoftPreconditions.checkArgument(trickplaySetting != TRICKPLAY_SETTING_NOT_SET);
    181         if (useContentProvider(context)) {
    182             setPreference(context, PREFS_KEY_TRICKPLAY_SETTING, trickplaySetting);
    183         } else {
    184             getSharedPreferences(context)
    185                     .edit()
    186                     .putInt(PREFS_KEY_TRICKPLAY_SETTING, trickplaySetting)
    187                     .apply();
    188         }
    189     }
    190 
    191     public static synchronized boolean getStoreTsStream(Context context) {
    192         SoftPreconditions.checkState(sInitialized);
    193         if (useContentProvider(context)) {
    194             return sPreferenceValues.getBoolean(PREFS_KEY_STORE_TS_STREAM, false);
    195         } else {
    196             return getSharedPreferences(context).getBoolean(PREFS_KEY_STORE_TS_STREAM, false);
    197         }
    198     }
    199 
    200     public static synchronized void setStoreTsStream(Context context, boolean shouldStore) {
    201         if (useContentProvider(context)) {
    202             setPreference(context, PREFS_KEY_STORE_TS_STREAM, shouldStore);
    203         } else {
    204             getSharedPreferences(context)
    205                     .edit()
    206                     .putBoolean(PREFS_KEY_STORE_TS_STREAM, shouldStore)
    207                     .apply();
    208         }
    209     }
    210 
    211     public static synchronized String getLastPostalCode(Context context) {
    212         SoftPreconditions.checkState(sInitialized);
    213         if (useContentProvider(context)) {
    214             return sPreferenceValues.getString(PREFS_KEY_LAST_POSTAL_CODE);
    215         } else {
    216             return getSharedPreferences(context).getString(PREFS_KEY_LAST_POSTAL_CODE, null);
    217         }
    218     }
    219 
    220     public static synchronized void setLastPostalCode(Context context, String postalCode) {
    221         if (useContentProvider(context)) {
    222             setPreference(context, PREFS_KEY_LAST_POSTAL_CODE, postalCode);
    223         } else {
    224             getSharedPreferences(context)
    225                     .edit()
    226                     .putString(PREFS_KEY_LAST_POSTAL_CODE, postalCode)
    227                     .apply();
    228         }
    229     }
    230 
    231     protected static SharedPreferences getSharedPreferences(Context context) {
    232         return context.getSharedPreferences(SHARED_PREFS_NAME, Context.MODE_PRIVATE);
    233     }
    234 
    235     private static synchronized void setPreference(Context context, String key, String value) {
    236         sPreferenceValues.putString(key, value);
    237         savePreference(context, key, value);
    238     }
    239 
    240     private static synchronized void setPreference(Context context, String key, int value) {
    241         sPreferenceValues.putInt(key, value);
    242         savePreference(context, key, Integer.toString(value));
    243     }
    244 
    245     private static synchronized void setPreference(Context context, String key, long value) {
    246         sPreferenceValues.putLong(key, value);
    247         savePreference(context, key, Long.toString(value));
    248     }
    249 
    250     private static synchronized void setPreference(Context context, String key, boolean value) {
    251         sPreferenceValues.putBoolean(key, value);
    252         savePreference(context, key, Boolean.toString(value));
    253     }
    254 
    255     private static void savePreference(
    256             final Context context, final String key, final String value) {
    257         new AsyncTask<Void, Void, Void>() {
    258             @Override
    259             protected Void doInBackground(Void... params) {
    260                 ContentResolver resolver = context.getContentResolver();
    261                 ContentValues values = new ContentValues();
    262                 values.put(Preferences.COLUMN_KEY, key);
    263                 values.put(Preferences.COLUMN_VALUE, value);
    264                 try {
    265                     resolver.insert(Preferences.CONTENT_URI, values);
    266                 } catch (Exception e) {
    267                     SoftPreconditions.warn(
    268                             TAG, "setPreference", e, "Error writing preference values");
    269                 }
    270                 return null;
    271             }
    272         }.execute();
    273     }
    274 
    275     private static class LoadPreferencesTask extends AsyncTask<Void, Void, Bundle> {
    276         private final Context mContext;
    277 
    278         private LoadPreferencesTask(Context context) {
    279             mContext = context;
    280         }
    281 
    282         @Override
    283         protected Bundle doInBackground(Void... params) {
    284             Bundle bundle = new Bundle();
    285             ContentResolver resolver = mContext.getContentResolver();
    286             String[] projection = new String[] {Preferences.COLUMN_KEY, Preferences.COLUMN_VALUE};
    287             try (Cursor cursor =
    288                     resolver.query(Preferences.CONTENT_URI, projection, null, null, null)) {
    289                 if (cursor != null) {
    290                     while (!isCancelled() && cursor.moveToNext()) {
    291                         String key = cursor.getString(0);
    292                         String value = cursor.getString(1);
    293                         Class prefClass = sPref2TypeMapping.get(key);
    294                         if (prefClass == int.class) {
    295                             try {
    296                                 bundle.putInt(key, Integer.parseInt(value));
    297                             } catch (NumberFormatException e) {
    298                                 Log.w(TAG, "Invalid format, key=" + key + ", value=" + value);
    299                             }
    300                         } else if (prefClass == long.class) {
    301                             try {
    302                                 bundle.putLong(key, Long.parseLong(value));
    303                             } catch (NumberFormatException e) {
    304                                 Log.w(TAG, "Invalid format, key=" + key + ", value=" + value);
    305                             }
    306                         } else if (prefClass == boolean.class) {
    307                             bundle.putBoolean(key, Boolean.parseBoolean(value));
    308                         } else {
    309                             bundle.putString(key, value);
    310                         }
    311                     }
    312                 }
    313             } catch (Exception e) {
    314                 SoftPreconditions.warn(TAG, "getPreference", e, "Error querying preference values");
    315                 return null;
    316             }
    317             return bundle;
    318         }
    319 
    320         @Override
    321         protected void onPostExecute(Bundle bundle) {
    322             synchronized (CommonPreferences.class) {
    323                 if (bundle != null) {
    324                     sPreferenceValues.putAll(bundle);
    325                 }
    326             }
    327             if (sPreferencesChangedListener != null) {
    328                 sPreferencesChangedListener.onCommonPreferencesChanged();
    329             }
    330         }
    331     }
    332 }
    333