1/*
2 * Copyright (C) 2014 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
17package com.android.inputmethod.latin;
18
19import android.app.DownloadManager;
20import android.content.BroadcastReceiver;
21import android.content.ComponentName;
22import android.content.Context;
23import android.content.Intent;
24import android.content.SharedPreferences;
25import android.content.pm.ApplicationInfo;
26import android.content.pm.PackageManager;
27import android.database.Cursor;
28import android.os.Process;
29import android.preference.PreferenceManager;
30import android.util.Log;
31import android.view.inputmethod.InputMethodManager;
32import android.view.inputmethod.InputMethodSubtype;
33
34import com.android.inputmethod.dictionarypack.DictionaryPackConstants;
35import com.android.inputmethod.dictionarypack.DownloadManagerWrapper;
36import com.android.inputmethod.keyboard.KeyboardLayoutSet;
37import com.android.inputmethod.latin.settings.Settings;
38import com.android.inputmethod.latin.setup.SetupActivity;
39import com.android.inputmethod.latin.utils.UncachedInputMethodManagerUtils;
40
41/**
42 * This class detects the {@link Intent#ACTION_MY_PACKAGE_REPLACED} broadcast intent when this IME
43 * package has been replaced by a newer version of the same package. This class also detects
44 * {@link Intent#ACTION_BOOT_COMPLETED} and {@link Intent#ACTION_USER_INITIALIZE} broadcast intent.
45 *
46 * If this IME has already been installed in the system image and a new version of this IME has
47 * been installed, {@link Intent#ACTION_MY_PACKAGE_REPLACED} is received by this receiver and it
48 * will hide the setup wizard's icon.
49 *
50 * If this IME has already been installed in the data partition and a new version of this IME has
51 * been installed, {@link Intent#ACTION_MY_PACKAGE_REPLACED} is received by this receiver but it
52 * will not hide the setup wizard's icon, and the icon will appear on the launcher.
53 *
54 * If this IME hasn't been installed yet and has been newly installed, no
55 * {@link Intent#ACTION_MY_PACKAGE_REPLACED} will be sent and the setup wizard's icon will appear
56 * on the launcher.
57 *
58 * When the device has been booted, {@link Intent#ACTION_BOOT_COMPLETED} is received by this
59 * receiver and it checks whether the setup wizard's icon should be appeared or not on the launcher
60 * depending on which partition this IME is installed.
61 *
62 * When the system locale has been changed, {@link Intent#ACTION_LOCALE_CHANGED} is received by
63 * this receiver and the {@link KeyboardLayoutSet}'s cache is cleared.
64 */
65public final class SystemBroadcastReceiver extends BroadcastReceiver {
66    private static final String TAG = SystemBroadcastReceiver.class.getSimpleName();
67
68    @Override
69    public void onReceive(final Context context, final Intent intent) {
70        final String intentAction = intent.getAction();
71        if (Intent.ACTION_MY_PACKAGE_REPLACED.equals(intentAction)) {
72            Log.i(TAG, "Package has been replaced: " + context.getPackageName());
73            // Need to restore additional subtypes because system always clears additional
74            // subtypes when the package is replaced.
75            RichInputMethodManager.init(context);
76            final RichInputMethodManager richImm = RichInputMethodManager.getInstance();
77            final InputMethodSubtype[] additionalSubtypes = richImm.getAdditionalSubtypes();
78            richImm.setAdditionalInputMethodSubtypes(additionalSubtypes);
79            toggleAppIcon(context);
80
81            // Remove all the previously scheduled downloads. This will also makes sure
82            // that any erroneously stuck downloads will get cleared. (b/21797386)
83            removeOldDownloads(context);
84            // b/21797386
85            // downloadLatestDictionaries(context);
86        } else if (Intent.ACTION_BOOT_COMPLETED.equals(intentAction)) {
87            Log.i(TAG, "Boot has been completed");
88            toggleAppIcon(context);
89        } else if (Intent.ACTION_LOCALE_CHANGED.equals(intentAction)) {
90            Log.i(TAG, "System locale changed");
91            KeyboardLayoutSet.onSystemLocaleChanged();
92        }
93
94        // The process that hosts this broadcast receiver is invoked and remains alive even after
95        // 1) the package has been re-installed,
96        // 2) the device has just booted,
97        // 3) a new user has been created.
98        // There is no good reason to keep the process alive if this IME isn't a current IME.
99        final InputMethodManager imm = (InputMethodManager)
100                context.getSystemService(Context.INPUT_METHOD_SERVICE);
101        // Called to check whether this IME has been triggered by the current user or not
102        final boolean isInputMethodManagerValidForUserOfThisProcess =
103                !imm.getInputMethodList().isEmpty();
104        final boolean isCurrentImeOfCurrentUser = isInputMethodManagerValidForUserOfThisProcess
105                && UncachedInputMethodManagerUtils.isThisImeCurrent(context, imm);
106        if (!isCurrentImeOfCurrentUser) {
107            final int myPid = Process.myPid();
108            Log.i(TAG, "Killing my process: pid=" + myPid);
109            Process.killProcess(myPid);
110        }
111    }
112
113    private void removeOldDownloads(Context context) {
114        try {
115            Log.i(TAG, "Removing the old downloads in progress of the previous keyboard version.");
116            final DownloadManagerWrapper downloadManagerWrapper = new DownloadManagerWrapper(
117                    context);
118            final DownloadManager.Query q = new DownloadManager.Query();
119            // Query all the download statuses except the succeeded ones.
120            q.setFilterByStatus(DownloadManager.STATUS_FAILED
121                    | DownloadManager.STATUS_PAUSED
122                    | DownloadManager.STATUS_PENDING
123                    | DownloadManager.STATUS_RUNNING);
124            final Cursor c = downloadManagerWrapper.query(q);
125            if (c != null) {
126                for (c.moveToFirst(); !c.isAfterLast(); c.moveToNext()) {
127                    final long downloadId = c
128                            .getLong(c.getColumnIndex(DownloadManager.COLUMN_ID));
129                    downloadManagerWrapper.remove(downloadId);
130                    Log.i(TAG, "Removed the download with Id: " + downloadId);
131                }
132                c.close();
133            }
134        } catch (Exception e) {
135            Log.e(TAG, "Exception while removing old downloads.");
136        }
137    }
138
139    private void downloadLatestDictionaries(Context context) {
140        final Intent updateIntent = new Intent(
141                DictionaryPackConstants.INIT_AND_UPDATE_NOW_INTENT_ACTION);
142        context.sendBroadcast(updateIntent);
143    }
144
145    public static void toggleAppIcon(final Context context) {
146        final int appInfoFlags = context.getApplicationInfo().flags;
147        final boolean isSystemApp = (appInfoFlags & ApplicationInfo.FLAG_SYSTEM) > 0;
148        if (Log.isLoggable(TAG, Log.INFO)) {
149            Log.i(TAG, "toggleAppIcon() : FLAG_SYSTEM = " + isSystemApp);
150        }
151        final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
152        context.getPackageManager().setComponentEnabledSetting(
153                new ComponentName(context, SetupActivity.class),
154                Settings.readShowSetupWizardIcon(prefs, context)
155                        ? PackageManager.COMPONENT_ENABLED_STATE_ENABLED
156                        : PackageManager.COMPONENT_ENABLED_STATE_DISABLED,
157                PackageManager.DONT_KILL_APP);
158    }
159}
160