diff --git a/Android.mk b/Android.mk index e5a712f191..28f0bc23bc 100644 --- a/Android.mk +++ b/Android.mk @@ -108,7 +108,9 @@ include $(CLEAR_VARS) LOCAL_USE_AAPT2 := true LOCAL_MODULE_TAGS := optional -LOCAL_STATIC_ANDROID_LIBRARIES := Launcher3CommonDepsLib +LOCAL_STATIC_ANDROID_LIBRARIES := \ + Launcher3CommonDepsLib \ + SecondaryDisplayLauncherLib LOCAL_SRC_FILES := \ $(call all-java-files-under, src) \ $(call all-java-files-under, src_shortcuts_overrides) \ diff --git a/SecondaryDisplayLauncher/Android.mk b/SecondaryDisplayLauncher/Android.mk new file mode 100644 index 0000000000..4fb5eba172 --- /dev/null +++ b/SecondaryDisplayLauncher/Android.mk @@ -0,0 +1,39 @@ +# +# Copyright (C) 2018 The Android Open Source Project +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +LOCAL_PATH := $(call my-dir) + +include $(CLEAR_VARS) +LOCAL_USE_AAPT2 := true +LOCAL_AAPT2_ONLY := true +LOCAL_MODULE_TAGS := optional + +LOCAL_STATIC_ANDROID_LIBRARIES := androidx.design_design + +LOCAL_STATIC_JAVA_LIBRARIES := LauncherPluginLib + +LOCAL_SRC_FILES := $(call all-java-files-under, src) + +LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/res + +LOCAL_PROGUARD_ENABLED := disabled + +LOCAL_SDK_VERSION := current +LOCAL_MIN_SDK_VERSION := 21 +LOCAL_MODULE := SecondaryDisplayLauncherLib +LOCAL_PRIVILEGED_MODULE := true + +include $(BUILD_STATIC_JAVA_LIBRARY) diff --git a/SecondaryDisplayLauncher/AndroidManifest.xml b/SecondaryDisplayLauncher/AndroidManifest.xml new file mode 100644 index 0000000000..ebf6b02cb8 --- /dev/null +++ b/SecondaryDisplayLauncher/AndroidManifest.xml @@ -0,0 +1,40 @@ + + + + + + + + + + + + + + + + diff --git a/SecondaryDisplayLauncher/res/drawable/ic_apps.xml b/SecondaryDisplayLauncher/res/drawable/ic_apps.xml new file mode 100644 index 0000000000..db779c2d12 --- /dev/null +++ b/SecondaryDisplayLauncher/res/drawable/ic_apps.xml @@ -0,0 +1,22 @@ + + + + + + diff --git a/SecondaryDisplayLauncher/res/drawable/ic_settings.xml b/SecondaryDisplayLauncher/res/drawable/ic_settings.xml new file mode 100644 index 0000000000..c269c3bafc --- /dev/null +++ b/SecondaryDisplayLauncher/res/drawable/ic_settings.xml @@ -0,0 +1,22 @@ + + + + + + diff --git a/SecondaryDisplayLauncher/res/layout-sw600dp/secondary_display_launcher.xml b/SecondaryDisplayLauncher/res/layout-sw600dp/secondary_display_launcher.xml new file mode 100644 index 0000000000..46f1674e47 --- /dev/null +++ b/SecondaryDisplayLauncher/res/layout-sw600dp/secondary_display_launcher.xml @@ -0,0 +1,56 @@ + + + + + + + + + + + + + + diff --git a/SecondaryDisplayLauncher/res/layout-sw720dp/secondary_display_launcher.xml b/SecondaryDisplayLauncher/res/layout-sw720dp/secondary_display_launcher.xml new file mode 100644 index 0000000000..6653a77968 --- /dev/null +++ b/SecondaryDisplayLauncher/res/layout-sw720dp/secondary_display_launcher.xml @@ -0,0 +1,57 @@ + + + + + + + + + + + + + + diff --git a/SecondaryDisplayLauncher/res/layout/app_grid_item.xml b/SecondaryDisplayLauncher/res/layout/app_grid_item.xml new file mode 100644 index 0000000000..ee5158ad32 --- /dev/null +++ b/SecondaryDisplayLauncher/res/layout/app_grid_item.xml @@ -0,0 +1,35 @@ + + + + + + + + + diff --git a/SecondaryDisplayLauncher/res/layout/app_picker_dialog.xml b/SecondaryDisplayLauncher/res/layout/app_picker_dialog.xml new file mode 100644 index 0000000000..563a1afa78 --- /dev/null +++ b/SecondaryDisplayLauncher/res/layout/app_picker_dialog.xml @@ -0,0 +1,34 @@ + + + + + + + diff --git a/SecondaryDisplayLauncher/res/layout/app_picker_layout.xml b/SecondaryDisplayLauncher/res/layout/app_picker_layout.xml new file mode 100644 index 0000000000..fbaeac4605 --- /dev/null +++ b/SecondaryDisplayLauncher/res/layout/app_picker_layout.xml @@ -0,0 +1,46 @@ + + + + + + + + + + + + diff --git a/SecondaryDisplayLauncher/res/layout/secondary_display_launcher.xml b/SecondaryDisplayLauncher/res/layout/secondary_display_launcher.xml new file mode 100644 index 0000000000..49cd499a50 --- /dev/null +++ b/SecondaryDisplayLauncher/res/layout/secondary_display_launcher.xml @@ -0,0 +1,56 @@ + + + + + + + + + + + + + + diff --git a/SecondaryDisplayLauncher/res/menu/context_menu.xml b/SecondaryDisplayLauncher/res/menu/context_menu.xml new file mode 100644 index 0000000000..626384229e --- /dev/null +++ b/SecondaryDisplayLauncher/res/menu/context_menu.xml @@ -0,0 +1,23 @@ + + + + + + + diff --git a/SecondaryDisplayLauncher/res/values-sw600dp/dimens.xml b/SecondaryDisplayLauncher/res/values-sw600dp/dimens.xml new file mode 100644 index 0000000000..f33a8db400 --- /dev/null +++ b/SecondaryDisplayLauncher/res/values-sw600dp/dimens.xml @@ -0,0 +1,22 @@ + + + + + 400dp + 400dp + 60dp + diff --git a/SecondaryDisplayLauncher/res/values-sw720dp/dimens.xml b/SecondaryDisplayLauncher/res/values-sw720dp/dimens.xml new file mode 100644 index 0000000000..524e52af71 --- /dev/null +++ b/SecondaryDisplayLauncher/res/values-sw720dp/dimens.xml @@ -0,0 +1,22 @@ + + + + + 660dp + 660dp + 70dp + diff --git a/SecondaryDisplayLauncher/res/values/colors.xml b/SecondaryDisplayLauncher/res/values/colors.xml new file mode 100644 index 0000000000..66b41a2ba6 --- /dev/null +++ b/SecondaryDisplayLauncher/res/values/colors.xml @@ -0,0 +1,22 @@ + + + + #884e8391 + diff --git a/SecondaryDisplayLauncher/res/values/dimens.xml b/SecondaryDisplayLauncher/res/values/dimens.xml new file mode 100644 index 0000000000..7cca60719c --- /dev/null +++ b/SecondaryDisplayLauncher/res/values/dimens.xml @@ -0,0 +1,31 @@ + + + + + 72dp + 24dp + 24dp + 64dp + 64dp + 24dp + 8dp + 8dp + + 300dp + 300dp + 20dp + 20dp + diff --git a/SecondaryDisplayLauncher/res/values/strings.xml b/SecondaryDisplayLauncher/res/values/strings.xml new file mode 100644 index 0000000000..0f906c4837 --- /dev/null +++ b/SecondaryDisplayLauncher/res/values/strings.xml @@ -0,0 +1,25 @@ + + + + + SecondaryDisplayLauncher + Couldn\'t launch the activity + Add app shortcut + Set wallpaper + diff --git a/SecondaryDisplayLauncher/res/values/styles.xml b/SecondaryDisplayLauncher/res/values/styles.xml new file mode 100644 index 0000000000..4bfda6a2a5 --- /dev/null +++ b/SecondaryDisplayLauncher/res/values/styles.xml @@ -0,0 +1,30 @@ + + + + + + diff --git a/SecondaryDisplayLauncher/src/com/android/launcher3/AppEntry.java b/SecondaryDisplayLauncher/src/com/android/launcher3/AppEntry.java new file mode 100644 index 0000000000..3017b81c1d --- /dev/null +++ b/SecondaryDisplayLauncher/src/com/android/launcher3/AppEntry.java @@ -0,0 +1,58 @@ +/** + * Copyright (c) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.launcher3; + +import android.content.ComponentName; +import android.content.Intent; +import android.content.pm.PackageManager; +import android.content.pm.ResolveInfo; +import android.graphics.drawable.Drawable; + +/** An entry that represents a single activity that can be launched. */ +public class AppEntry { + + private String mLabel; + private Drawable mIcon; + private Intent mLaunchIntent; + + AppEntry(ResolveInfo info, PackageManager packageManager) { + mLabel = info.loadLabel(packageManager).toString(); + mIcon = info.loadIcon(packageManager); + mLaunchIntent = new Intent(); + mLaunchIntent.setComponent(new ComponentName(info.activityInfo.packageName, + info.activityInfo.name)); + } + + String getLabel() { + return mLabel; + } + + Drawable getIcon() { + return mIcon; + } + + Intent getLaunchIntent() { return mLaunchIntent; } + + ComponentName getComponentName() { + return mLaunchIntent.getComponent(); + } + + @Override + public String toString() { + return mLabel; + } +} diff --git a/SecondaryDisplayLauncher/src/com/android/launcher3/AppListAdapter.java b/SecondaryDisplayLauncher/src/com/android/launcher3/AppListAdapter.java new file mode 100644 index 0000000000..aa115cb575 --- /dev/null +++ b/SecondaryDisplayLauncher/src/com/android/launcher3/AppListAdapter.java @@ -0,0 +1,63 @@ +/** + * Copyright (c) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.launcher3; + +import android.content.Context; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ArrayAdapter; +import android.widget.ImageView; +import android.widget.TextView; + +import com.android.launcher3.R; + +import java.util.List; + +/** Adapter for available apps list. */ +public class AppListAdapter extends ArrayAdapter { + private final LayoutInflater mInflater; + + AppListAdapter(Context context) { + super(context, android.R.layout.simple_list_item_2); + mInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE); + } + + void setData(List data) { + clear(); + if (data != null) { + addAll(data); + } + } + + @Override + public View getView(int position, View convertView, ViewGroup parent) { + View view; + + if (convertView == null) { + view = mInflater.inflate(R.layout.app_grid_item, parent, false); + } else { + view = convertView; + } + + AppEntry item = getItem(position); + ((ImageView)view.findViewById(R.id.app_icon)).setImageDrawable(item.getIcon()); + ((TextView)view.findViewById(R.id.app_name)).setText(item.getLabel()); + + return view; + } +} diff --git a/SecondaryDisplayLauncher/src/com/android/launcher3/AppListViewModel.java b/SecondaryDisplayLauncher/src/com/android/launcher3/AppListViewModel.java new file mode 100644 index 0000000000..914fd5e01a --- /dev/null +++ b/SecondaryDisplayLauncher/src/com/android/launcher3/AppListViewModel.java @@ -0,0 +1,126 @@ +/** + * Copyright (c) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.launcher3; + +import android.app.Application; +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.content.pm.PackageManager; +import android.content.pm.ResolveInfo; +import android.os.AsyncTask; + +import androidx.lifecycle.AndroidViewModel; +import androidx.lifecycle.LiveData; + +import java.util.ArrayList; +import java.util.List; + +/** + * A view model that provides a list of activities that can be launched. + */ +public class AppListViewModel extends AndroidViewModel { + + private final AppListLiveData mLiveData; + private final PackageIntentReceiver + mPackageIntentReceiver; + + public AppListViewModel(Application application) { + super(application); + mLiveData = new AppListLiveData(application); + mPackageIntentReceiver = new PackageIntentReceiver(mLiveData, application); + } + + public LiveData> getAppList() { + return mLiveData; + } + + protected void onCleared() { + getApplication().unregisterReceiver(mPackageIntentReceiver); + } +} + +class AppListLiveData extends LiveData> { + + private final PackageManager mPackageManager; + private int mCurrentDataVersion; + + public AppListLiveData(Context context) { + mPackageManager = context.getPackageManager(); + loadData(); + } + + void loadData() { + final int loadDataVersion = ++mCurrentDataVersion; + + new AsyncTask>() { + @Override + protected List doInBackground(Void... voids) { + Intent mainIntent = new Intent(Intent.ACTION_MAIN, null); + mainIntent.addCategory(Intent.CATEGORY_LAUNCHER); + + List apps = mPackageManager.queryIntentActivities(mainIntent, + PackageManager.GET_META_DATA); + + List entries = new ArrayList<>(); + if (apps != null) { + for (ResolveInfo app : apps) { + AppEntry entry = new AppEntry(app, mPackageManager); + entries.add(entry); + } + } + return entries; + } + + @Override + protected void onPostExecute(List data) { + if (mCurrentDataVersion == loadDataVersion) { + setValue(data); + } + } + }.execute(); + } +} + +/** + * Receiver used to notify live data about app list changes. + */ +class PackageIntentReceiver extends BroadcastReceiver { + + private final AppListLiveData mLiveData; + + public PackageIntentReceiver(AppListLiveData liveData, Context context) { + mLiveData = liveData; + IntentFilter filter = new IntentFilter(Intent.ACTION_PACKAGE_ADDED); + filter.addAction(Intent.ACTION_PACKAGE_REMOVED); + filter.addAction(Intent.ACTION_PACKAGE_CHANGED); + filter.addDataScheme("package"); + context.registerReceiver(this, filter); + + // Register for events related to sdcard installation. + IntentFilter sdFilter = new IntentFilter(); + sdFilter.addAction(Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE); + sdFilter.addAction(Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE); + context.registerReceiver(this, sdFilter); + } + + @Override + public void onReceive(Context context, Intent intent) { + mLiveData.loadData(); + } +} \ No newline at end of file diff --git a/SecondaryDisplayLauncher/src/com/android/launcher3/PinnedAppListViewModel.java b/SecondaryDisplayLauncher/src/com/android/launcher3/PinnedAppListViewModel.java new file mode 100644 index 0000000000..4f92038ae4 --- /dev/null +++ b/SecondaryDisplayLauncher/src/com/android/launcher3/PinnedAppListViewModel.java @@ -0,0 +1,120 @@ +/** + * Copyright (c) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.launcher3; + +import static com.android.launcher3.PinnedAppListViewModel.PINNED_APPS_KEY; + +import android.app.Application; +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.content.SharedPreferences; +import android.content.pm.PackageManager; +import android.content.pm.ResolveInfo; +import android.os.AsyncTask; + +import androidx.lifecycle.AndroidViewModel; +import androidx.lifecycle.LiveData; + +import java.util.ArrayList; +import java.util.List; +import java.util.Set; + +/** + * A view model that provides a list of activities that were pinned by user to always display on + * home screen. + * The pinned activities are stored in {@link SharedPreferences} to keep the sample simple :). + */ +public class PinnedAppListViewModel extends AndroidViewModel { + + final static String PINNED_APPS_KEY = "pinned_apps"; + + private final PinnedAppListLiveData mLiveData; + + public PinnedAppListViewModel(Application application) { + super(application); + mLiveData = new PinnedAppListLiveData(application); + } + + public LiveData> getPinnedAppList() { + return mLiveData; + } +} + +class PinnedAppListLiveData extends LiveData> { + + private final Context mContext; + private final PackageManager mPackageManager; + // Store listener reference, so it won't be GC-ed. + private final SharedPreferences.OnSharedPreferenceChangeListener mChangeListener; + private int mCurrentDataVersion; + + public PinnedAppListLiveData(Context context) { + mContext = context; + mPackageManager = context.getPackageManager(); + + final SharedPreferences prefs = context.getSharedPreferences(PINNED_APPS_KEY, 0); + mChangeListener = (preferences, key) -> { + loadData(); + }; + prefs.registerOnSharedPreferenceChangeListener(mChangeListener); + + loadData(); + } + + private void loadData() { + final int loadDataVersion = ++mCurrentDataVersion; + + new AsyncTask>() { + @Override + protected List doInBackground(Void... voids) { + List entries = new ArrayList<>(); + + final SharedPreferences sp = mContext.getSharedPreferences(PINNED_APPS_KEY, 0); + final Set pinnedAppsComponents = sp.getStringSet(PINNED_APPS_KEY, null); + if (pinnedAppsComponents == null) { + return null; + } + + for (String componentString : pinnedAppsComponents) { + final Intent mainIntent = new Intent(Intent.ACTION_MAIN, null); + mainIntent.setComponent(ComponentName.unflattenFromString(componentString)); + mainIntent.addCategory(Intent.CATEGORY_LAUNCHER); + + final List apps = mPackageManager.queryIntentActivities(mainIntent, + PackageManager.GET_META_DATA); + + if (apps != null) { + for (ResolveInfo app : apps) { + final AppEntry entry = new AppEntry(app, mPackageManager); + entries.add(entry); + } + } + } + + return entries; + } + + @Override + protected void onPostExecute(List data) { + if (mCurrentDataVersion == loadDataVersion) { + setValue(data); + } + } + }.execute(); + } +} \ No newline at end of file diff --git a/SecondaryDisplayLauncher/src/com/android/launcher3/PinnedAppPickerDialog.java b/SecondaryDisplayLauncher/src/com/android/launcher3/PinnedAppPickerDialog.java new file mode 100644 index 0000000000..02e6e4a9c6 --- /dev/null +++ b/SecondaryDisplayLauncher/src/com/android/launcher3/PinnedAppPickerDialog.java @@ -0,0 +1,74 @@ +/** + * Copyright (c) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.launcher3; + +import android.os.Bundle; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.GridView; + +import androidx.fragment.app.DialogFragment; + +import com.android.launcher3.R; + +/** + * Callback to be invoked when an app was picked. + */ +interface AppPickedCallback { + void onAppPicked(AppEntry appEntry); +} + +/** + * Dialog that provides the user with a list of available apps to pin to the home screen. + */ +public class PinnedAppPickerDialog extends DialogFragment { + + private AppListAdapter mAppListAdapter; + private AppPickedCallback mAppPickerCallback; + + public PinnedAppPickerDialog() { + } + + public static PinnedAppPickerDialog newInstance(AppListAdapter appListAdapter, + AppPickedCallback callback) { + PinnedAppPickerDialog + frag = new PinnedAppPickerDialog(); + frag.mAppListAdapter = appListAdapter; + frag.mAppPickerCallback = callback; + return frag; + } + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, + Bundle savedInstanceState) { + return inflater.inflate(R.layout.app_picker_dialog, container); + } + + @Override + public void onViewCreated(View view, Bundle savedInstanceState) { + super.onViewCreated(view, savedInstanceState); + + GridView appGridView = view.findViewById(R.id.picker_app_grid); + appGridView.setAdapter(mAppListAdapter); + appGridView.setOnItemClickListener((adapterView, itemView, position, id) -> { + final AppEntry entry = mAppListAdapter.getItem(position); + mAppPickerCallback.onAppPicked(entry); + dismiss(); + }); + } +} \ No newline at end of file diff --git a/SecondaryDisplayLauncher/src/com/android/launcher3/SecondaryDisplayLauncher.java b/SecondaryDisplayLauncher/src/com/android/launcher3/SecondaryDisplayLauncher.java new file mode 100644 index 0000000000..5e8b40246a --- /dev/null +++ b/SecondaryDisplayLauncher/src/com/android/launcher3/SecondaryDisplayLauncher.java @@ -0,0 +1,217 @@ +/** + * Copyright (c) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.launcher3; + +import static com.android.launcher3.PinnedAppListViewModel.PINNED_APPS_KEY; + +import android.animation.Animator; +import android.animation.AnimatorListenerAdapter; +import android.app.AlertDialog; +import android.app.Application; +import android.content.Intent; +import android.content.SharedPreferences; +import android.content.res.Configuration; +import android.os.Bundle; +import android.support.design.circularreveal.cardview.CircularRevealCardView; +import android.support.design.widget.FloatingActionButton; +import android.view.MenuInflater; +import android.view.MenuItem; +import android.view.View; +import android.view.ViewAnimationUtils; +import android.widget.GridView; +import android.widget.ImageButton; +import android.widget.PopupMenu; + +import androidx.fragment.app.FragmentActivity; +import androidx.fragment.app.FragmentManager; +import androidx.lifecycle.ViewModelProvider; +import androidx.lifecycle.ViewModelProvider.AndroidViewModelFactory; + +import java.util.HashSet; +import java.util.Set; + +/** + * Secondary launcher activity. It's launch mode is configured as "singleTop" to allow showing on + * multiple displays and to ensure a single instance per each display. + */ +public class SecondaryDisplayLauncher extends FragmentActivity implements AppPickedCallback, + PopupMenu.OnMenuItemClickListener { + + private AppListAdapter mAppListAdapter; + private AppListAdapter mPinnedAppListAdapter; + private CircularRevealCardView mAppDrawerView; + private FloatingActionButton mFab; + + private boolean mAppDrawerShown; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.secondary_display_launcher); + + mAppDrawerView = findViewById(R.id.FloatingSheet); + mFab = findViewById(R.id.FloatingActionButton); + + mFab.setOnClickListener((View v) -> { + showAppDrawer(true); + }); + + final ViewModelProvider viewModelProvider = new ViewModelProvider(getViewModelStore(), + new AndroidViewModelFactory((Application) getApplicationContext())); + + mPinnedAppListAdapter = new AppListAdapter(this); + final GridView pinnedAppGridView = findViewById(R.id.pinned_app_grid); + pinnedAppGridView.setAdapter(mPinnedAppListAdapter); + pinnedAppGridView.setOnItemClickListener((adapterView, view, position, id) -> { + final AppEntry entry = mPinnedAppListAdapter.getItem(position); + launch(entry.getLaunchIntent()); + }); + final PinnedAppListViewModel pinnedAppListViewModel = + viewModelProvider.get(PinnedAppListViewModel.class); + pinnedAppListViewModel.getPinnedAppList().observe(this, data -> { + mPinnedAppListAdapter.setData(data); + }); + + mAppListAdapter = new AppListAdapter(this); + final GridView appGridView = findViewById(R.id.app_grid); + appGridView.setAdapter(mAppListAdapter); + appGridView.setOnItemClickListener((adapterView, view, position, id) -> { + final AppEntry entry = mAppListAdapter.getItem(position); + launch(entry.getLaunchIntent()); + }); + final AppListViewModel appListViewModel = viewModelProvider.get(AppListViewModel.class); + appListViewModel.getAppList().observe(this, data -> { + mAppListAdapter.setData(data); + }); + + ImageButton optionsButton = findViewById(R.id.OptionsButton); + optionsButton.setOnClickListener((View v) -> { + PopupMenu popup = new PopupMenu(this,v); + popup.setOnMenuItemClickListener(this); + MenuInflater inflater = popup.getMenuInflater(); + inflater.inflate(R.menu.context_menu, popup.getMenu()); + popup.show(); + }); + } + + @Override + public boolean onMenuItemClick(MenuItem item) { + // Respond to picking one of the popup menu items. + final int id = item.getItemId(); + if (id == R.id.add_app_shortcut) { + FragmentManager fm = getSupportFragmentManager(); + PinnedAppPickerDialog pickerDialogFragment = + PinnedAppPickerDialog.newInstance(mAppListAdapter, this); + pickerDialogFragment.show(fm, "fragment_app_picker"); + return true; + } else if (id == R.id.set_wallpaper) { + Intent intent = new Intent(Intent.ACTION_SET_WALLPAPER); + startActivity(Intent.createChooser(intent, getString(R.string.set_wallpaper))); + return true; + } + + return true; + } + + @Override + public void onConfigurationChanged(Configuration newConfig) { + super.onConfigurationChanged(newConfig); + showAppDrawer(false); + } + + public void onBackPressed() { + // If the app drawer was shown - hide it. Otherwise, not doing anything since we don't want + // to close the launcher. + showAppDrawer(false); + } + + public void onNewIntent(Intent intent) { + super.onNewIntent(intent); + // A new intent will bring the launcher to top. Hide the app drawer to reset the state. + showAppDrawer(false); + } + + void launch(Intent launchIntent) { + launchIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + try { + startActivity(launchIntent); + } catch (Exception e) { + final AlertDialog.Builder builder = + new AlertDialog.Builder(this, android.R.style.Theme_Material_Dialog_Alert); + builder.setTitle(R.string.couldnt_launch) + .setMessage(e.getLocalizedMessage()) + .setIcon(android.R.drawable.ic_dialog_alert) + .show(); + } + } + + /** + * Store the picked app to persistent pinned list and update the loader. + */ + @Override + public void onAppPicked(AppEntry appEntry) { + final SharedPreferences sp = getSharedPreferences(PINNED_APPS_KEY, 0); + Set pinnedApps = sp.getStringSet(PINNED_APPS_KEY, null); + if (pinnedApps == null) { + pinnedApps = new HashSet(); + } else { + // Always need to create a new object to make sure that the changes are persisted. + pinnedApps = new HashSet(pinnedApps); + } + pinnedApps.add(appEntry.getComponentName().flattenToString()); + + final SharedPreferences.Editor editor = sp.edit(); + editor.putStringSet(PINNED_APPS_KEY, pinnedApps); + editor.apply(); + } + + /** + * Show/hide app drawer card with animation. + */ + private void showAppDrawer(boolean show) { + if (show == mAppDrawerShown) { + return; + } + + final Animator animator = revealAnimator(mAppDrawerView, show); + if (show) { + mAppDrawerShown = true; + mAppDrawerView.setVisibility(View.VISIBLE); + mFab.setVisibility(View.INVISIBLE); + } else { + mAppDrawerShown = false; + animator.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + super.onAnimationEnd(animation); + mAppDrawerView.setVisibility(View.INVISIBLE); + mFab.setVisibility(View.VISIBLE); + } + }); + } + animator.start(); + } + + /** + * Create reveal/hide animator for app list card. + */ + private Animator revealAnimator(View view, boolean open) { + final int radius = (int) Math.hypot((double) view.getWidth(), (double) view.getHeight()); + return ViewAnimationUtils.createCircularReveal(view, view.getRight(), view.getBottom(), + open ? 0 : radius, open ? radius : 0); + } +} diff --git a/quickstep/libs/sysui_shared.jar b/quickstep/libs/sysui_shared.jar index 5958d02029..6e7534dc6d 100644 Binary files a/quickstep/libs/sysui_shared.jar and b/quickstep/libs/sysui_shared.jar differ diff --git a/quickstep/res/values-mr/strings.xml b/quickstep/res/values-mr/strings.xml index f88bb77f4b..fa8574a3cf 100644 --- a/quickstep/res/values-mr/strings.xml +++ b/quickstep/res/values-mr/strings.xml @@ -25,7 +25,7 @@ "अवलोकन" "कोणतेही अलीकडील आयटम नाहीत" "बंद" - "अॅप वापर सेटिंग्ज" + "अ‍ॅप वापर सेटिंग्ज" "सर्व साफ करा" "अलीकडील अॅप्स" diff --git a/quickstep/src/com/android/launcher3/LauncherAppTransitionManagerImpl.java b/quickstep/src/com/android/launcher3/LauncherAppTransitionManagerImpl.java index 1049cca597..a084ca8078 100644 --- a/quickstep/src/com/android/launcher3/LauncherAppTransitionManagerImpl.java +++ b/quickstep/src/com/android/launcher3/LauncherAppTransitionManagerImpl.java @@ -80,8 +80,8 @@ import com.android.systemui.shared.system.RemoteAnimationAdapterCompat; import com.android.systemui.shared.system.RemoteAnimationDefinitionCompat; import com.android.systemui.shared.system.RemoteAnimationRunnerCompat; import com.android.systemui.shared.system.RemoteAnimationTargetCompat; -import com.android.systemui.shared.system.SyncRtSurfaceTransactionApplier; -import com.android.systemui.shared.system.SyncRtSurfaceTransactionApplier.SurfaceParams; +import com.android.systemui.shared.system.SyncRtSurfaceTransactionApplierCompat; +import com.android.systemui.shared.system.SyncRtSurfaceTransactionApplierCompat.SurfaceParams; import com.android.systemui.shared.system.WindowManagerWrapper; /** @@ -580,8 +580,8 @@ public class LauncherAppTransitionManagerImpl extends LauncherAppTransitionManag MODE_OPENING); RemoteAnimationTargetSet closingTargets = new RemoteAnimationTargetSet(targets, MODE_CLOSING); - SyncRtSurfaceTransactionApplier surfaceApplier = new SyncRtSurfaceTransactionApplier( - mFloatingView); + SyncRtSurfaceTransactionApplierCompat surfaceApplier = + new SyncRtSurfaceTransactionApplierCompat(mFloatingView); ValueAnimator appAnimator = ValueAnimator.ofFloat(0, 1); appAnimator.setDuration(APP_LAUNCH_DURATION); @@ -618,7 +618,7 @@ public class LauncherAppTransitionManagerImpl extends LauncherAppTransitionManag // Animate window corner radius from 100% to windowCornerRadius. float windowCornerRadius = RecentsModel.INSTANCE.get(mLauncher) .getWindowCornerRadius(); - float circleRadius = scaledWindowWidth / 2f; + float circleRadius = iconWidth / 2f; float windowRadius = Utilities.mapRange(easePercent, circleRadius, windowCornerRadius); @@ -740,8 +740,8 @@ public class LauncherAppTransitionManagerImpl extends LauncherAppTransitionManag * Animator that controls the transformations of the windows the targets that are closing. */ private Animator getClosingWindowAnimators(RemoteAnimationTargetCompat[] targets) { - SyncRtSurfaceTransactionApplier surfaceApplier = - new SyncRtSurfaceTransactionApplier(mDragLayer); + SyncRtSurfaceTransactionApplierCompat surfaceApplier = + new SyncRtSurfaceTransactionApplierCompat(mDragLayer); Matrix matrix = new Matrix(); ValueAnimator closingAnimator = ValueAnimator.ofFloat(0, 1); int duration = CLOSING_TRANSITION_DURATION_MS; diff --git a/quickstep/src/com/android/quickstep/ActivityControlHelper.java b/quickstep/src/com/android/quickstep/ActivityControlHelper.java index 16f5fa1142..f60572cc32 100644 --- a/quickstep/src/com/android/quickstep/ActivityControlHelper.java +++ b/quickstep/src/com/android/quickstep/ActivityControlHelper.java @@ -45,6 +45,7 @@ import android.os.Handler; import android.os.Looper; import android.view.View; +import com.android.launcher3.BaseActivity; import com.android.launcher3.BaseDraggingActivity; import com.android.launcher3.DeviceProfile; import com.android.launcher3.Launcher; @@ -114,6 +115,11 @@ public interface ActivityControlHelper { @Nullable T getCreatedActivity(); + default boolean isResumed() { + BaseDraggingActivity activity = getCreatedActivity(); + return activity != null && activity.hasBeenResumed(); + } + @UiThread @Nullable RecentsView getVisibleRecentsView(); diff --git a/quickstep/src/com/android/quickstep/LauncherSearchIndexablesProvider.java b/quickstep/src/com/android/quickstep/LauncherSearchIndexablesProvider.java index f5e1f6ec56..5d6fbe2ca6 100644 --- a/quickstep/src/com/android/quickstep/LauncherSearchIndexablesProvider.java +++ b/quickstep/src/com/android/quickstep/LauncherSearchIndexablesProvider.java @@ -29,7 +29,6 @@ import android.provider.SearchIndexablesProvider; import android.util.Xml; import com.android.launcher3.R; -import com.android.launcher3.graphics.IconShapeOverride; import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; @@ -88,8 +87,6 @@ public class LauncherSearchIndexablesProvider extends SearchIndexablesProvider { } catch (IOException |XmlPullParserException e) { throw new RuntimeException(e); } - } else if (!IconShapeOverride.isSupported(getContext())) { - cursor.addRow(new String[] {IconShapeOverride.KEY_PREFERENCE}); } return cursor; } diff --git a/quickstep/src/com/android/quickstep/OtherActivityTouchConsumer.java b/quickstep/src/com/android/quickstep/OtherActivityTouchConsumer.java index b11260ea58..95be188a0e 100644 --- a/quickstep/src/com/android/quickstep/OtherActivityTouchConsumer.java +++ b/quickstep/src/com/android/quickstep/OtherActivityTouchConsumer.java @@ -22,6 +22,8 @@ import static android.view.MotionEvent.ACTION_POINTER_UP; import static android.view.MotionEvent.ACTION_UP; import static android.view.MotionEvent.INVALID_POINTER_ID; +import static com.android.launcher3.util.RaceConditionTracker.ENTER; +import static com.android.launcher3.util.RaceConditionTracker.EXIT; import static com.android.systemui.shared.system.ActivityManagerWrapper .CLOSE_SYSTEM_WINDOWS_REASON_RECENTS; import static com.android.systemui.shared.system.RemoteAnimationTargetCompat.MODE_CLOSING; @@ -47,6 +49,7 @@ import android.view.ViewConfiguration; import android.view.WindowManager; import com.android.launcher3.MainThreadExecutor; +import com.android.launcher3.util.RaceConditionTracker; import com.android.launcher3.util.TraceHelper; import com.android.quickstep.util.RemoteAnimationTargetSet; import com.android.systemui.shared.system.ActivityManagerWrapper; @@ -70,6 +73,7 @@ import java.util.concurrent.TimeUnit; public class OtherActivityTouchConsumer extends ContextWrapper implements TouchConsumer { private static final long LAUNCHER_DRAW_TIMEOUT_MS = 150; + public static final String DOWN_EVT = "OtherActivityTouchConsumer.DOWN"; private final SparseArray mAnimationStates = new SparseArray<>(); private final RunningTaskInfo mRunningTask; @@ -135,6 +139,7 @@ public class OtherActivityTouchConsumer extends ContextWrapper implements TouchC mTouchInteractionLog.addMotionEvent(ev); switch (ev.getActionMasked()) { case ACTION_DOWN: { + RaceConditionTracker.onEvent(DOWN_EVT, ENTER); TraceHelper.beginSection("TouchInt"); mActivePointerId = ev.getPointerId(0); mDownPos.set(ev.getX(), ev.getY()); @@ -151,6 +156,7 @@ public class OtherActivityTouchConsumer extends ContextWrapper implements TouchC Display display = getSystemService(WindowManager.class).getDefaultDisplay(); mDisplayRotation = display.getRotation(); WindowManagerWrapper.getInstance().getStableInsets(mStableInsets); + RaceConditionTracker.onEvent(DOWN_EVT, EXIT); break; } case ACTION_POINTER_UP: { diff --git a/quickstep/src/com/android/quickstep/OverviewCommandHelper.java b/quickstep/src/com/android/quickstep/OverviewCommandHelper.java index 61bb75f857..b3f8a113c5 100644 --- a/quickstep/src/com/android/quickstep/OverviewCommandHelper.java +++ b/quickstep/src/com/android/quickstep/OverviewCommandHelper.java @@ -66,7 +66,7 @@ import com.android.systemui.shared.system.ActivityManagerWrapper; import com.android.systemui.shared.system.LatencyTrackerCompat; import com.android.systemui.shared.system.PackageManagerWrapper; import com.android.systemui.shared.system.RemoteAnimationTargetCompat; -import com.android.systemui.shared.system.SyncRtSurfaceTransactionApplier; +import com.android.systemui.shared.system.SyncRtSurfaceTransactionApplierCompat; import com.android.systemui.shared.system.TransactionCompat; import java.util.ArrayList; @@ -343,8 +343,8 @@ public class OverviewCommandHelper { clipHelper.updateTargetRect(targetRect); clipHelper.prepareAnimation(false /* isOpening */); - SyncRtSurfaceTransactionApplier syncTransactionApplier = - new SyncRtSurfaceTransactionApplier(rootView); + SyncRtSurfaceTransactionApplierCompat syncTransactionApplier = + new SyncRtSurfaceTransactionApplierCompat(rootView); ValueAnimator valueAnimator = ValueAnimator.ofFloat(0, 1); valueAnimator.setDuration(RECENTS_LAUNCH_DURATION); valueAnimator.setInterpolator(TOUCH_RESPONSE_INTERPOLATOR); diff --git a/quickstep/src/com/android/quickstep/TaskUtils.java b/quickstep/src/com/android/quickstep/TaskUtils.java index 4b86a7fa88..cab3a4cdf2 100644 --- a/quickstep/src/com/android/quickstep/TaskUtils.java +++ b/quickstep/src/com/android/quickstep/TaskUtils.java @@ -43,7 +43,7 @@ import com.android.quickstep.views.RecentsView; import com.android.quickstep.views.TaskView; import com.android.systemui.shared.recents.model.Task; import com.android.systemui.shared.system.RemoteAnimationTargetCompat; -import com.android.systemui.shared.system.SyncRtSurfaceTransactionApplier; +import com.android.systemui.shared.system.SyncRtSurfaceTransactionApplierCompat; import java.util.List; @@ -145,8 +145,8 @@ public class TaskUtils { */ public static ValueAnimator getRecentsWindowAnimator(TaskView v, boolean skipViewChanges, RemoteAnimationTargetCompat[] targets, final ClipAnimationHelper inOutHelper) { - SyncRtSurfaceTransactionApplier syncTransactionApplier = - new SyncRtSurfaceTransactionApplier(v); + SyncRtSurfaceTransactionApplierCompat syncTransactionApplier = + new SyncRtSurfaceTransactionApplierCompat(v); final ValueAnimator appAnimator = ValueAnimator.ofFloat(0, 1); appAnimator.setInterpolator(TOUCH_RESPONSE_INTERPOLATOR); appAnimator.addUpdateListener(new MultiValueUpdateListener() { diff --git a/quickstep/src/com/android/quickstep/TouchInteractionService.java b/quickstep/src/com/android/quickstep/TouchInteractionService.java index b1a214d9dd..a42ee094f4 100644 --- a/quickstep/src/com/android/quickstep/TouchInteractionService.java +++ b/quickstep/src/com/android/quickstep/TouchInteractionService.java @@ -249,7 +249,7 @@ public class TouchInteractionService extends Service { if (runningTaskInfo == null && !forceToLauncher) { return TouchConsumer.NO_OP; } else if (forceToLauncher || - runningTaskInfo.topActivity.equals(mOverviewCommandHelper.overviewComponent)) { + mOverviewCommandHelper.getActivityControlHelper().isResumed()) { return OverviewTouchConsumer.newInstance( mOverviewCommandHelper.getActivityControlHelper(), false, mTouchInteractionLog); } else { @@ -257,10 +257,10 @@ public class TouchInteractionService extends Service { tracker = VelocityTracker.obtain(); } return new OtherActivityTouchConsumer(this, runningTaskInfo, mRecentsModel, - mOverviewCommandHelper.overviewIntent, - mOverviewCommandHelper.getActivityControlHelper(), mMainThreadExecutor, - mBackgroundThreadChoreographer, downHitTarget, mOverviewCallbacks, - mTaskOverlayFactory, mInputConsumer, tracker, mTouchInteractionLog); + mOverviewCommandHelper.overviewIntent, + mOverviewCommandHelper.getActivityControlHelper(), mMainThreadExecutor, + mBackgroundThreadChoreographer, downHitTarget, mOverviewCallbacks, + mTaskOverlayFactory, mInputConsumer, tracker, mTouchInteractionLog); } } diff --git a/quickstep/src/com/android/quickstep/WindowTransformSwipeHandler.java b/quickstep/src/com/android/quickstep/WindowTransformSwipeHandler.java index 78a2055bb5..6e2e3d3d7c 100644 --- a/quickstep/src/com/android/quickstep/WindowTransformSwipeHandler.java +++ b/quickstep/src/com/android/quickstep/WindowTransformSwipeHandler.java @@ -85,7 +85,7 @@ import com.android.systemui.shared.system.InputConsumerController; import com.android.systemui.shared.system.LatencyTrackerCompat; import com.android.systemui.shared.system.RecentsAnimationControllerCompat; import com.android.systemui.shared.system.RemoteAnimationTargetCompat; -import com.android.systemui.shared.system.SyncRtSurfaceTransactionApplier; +import com.android.systemui.shared.system.SyncRtSurfaceTransactionApplierCompat; import com.android.systemui.shared.system.WindowCallbacksCompat; import java.util.StringJoiner; @@ -213,7 +213,7 @@ public class WindowTransformSwipeHandler { private T mActivity; private LayoutListener mLayoutListener; private RecentsView mRecentsView; - private SyncRtSurfaceTransactionApplier mSyncTransactionApplier; + private SyncRtSurfaceTransactionApplierCompat mSyncTransactionApplier; private QuickScrubController mQuickScrubController; private AnimationFactory mAnimationFactory = (t, i) -> { }; @@ -403,7 +403,9 @@ public class WindowTransformSwipeHandler { } mRecentsView = activity.getOverviewPanel(); - mSyncTransactionApplier = new SyncRtSurfaceTransactionApplier(mRecentsView); + SyncRtSurfaceTransactionApplierCompat.create(mRecentsView, (applier) -> { + mSyncTransactionApplier = applier; + }); mQuickScrubController = mRecentsView.getQuickScrubController(); mLayoutListener = mActivityControlHelper.createLayoutListener(mActivity); diff --git a/quickstep/src/com/android/quickstep/util/ClipAnimationHelper.java b/quickstep/src/com/android/quickstep/util/ClipAnimationHelper.java index 1e1f147c16..f6af67a30f 100644 --- a/quickstep/src/com/android/quickstep/util/ClipAnimationHelper.java +++ b/quickstep/src/com/android/quickstep/util/ClipAnimationHelper.java @@ -46,8 +46,8 @@ import com.android.quickstep.views.TaskThumbnailView; import com.android.systemui.shared.recents.ISystemUiProxy; import com.android.systemui.shared.recents.utilities.RectFEvaluator; import com.android.systemui.shared.system.RemoteAnimationTargetCompat; -import com.android.systemui.shared.system.SyncRtSurfaceTransactionApplier; -import com.android.systemui.shared.system.SyncRtSurfaceTransactionApplier.SurfaceParams; +import com.android.systemui.shared.system.SyncRtSurfaceTransactionApplierCompat; +import com.android.systemui.shared.system.SyncRtSurfaceTransactionApplierCompat.SurfaceParams; import com.android.systemui.shared.system.TransactionCompat; import com.android.systemui.shared.system.WindowManagerWrapper; @@ -152,7 +152,7 @@ public class ClipAnimationHelper { } public RectF applyTransform(RemoteAnimationTargetSet targetSet, float progress, - @Nullable SyncRtSurfaceTransactionApplier syncTransactionApplier) { + @Nullable SyncRtSurfaceTransactionApplierCompat syncTransactionApplier) { RectF currentRect; mTmpRectF.set(mTargetRect); Utilities.scaleRectFAboutCenter(mTmpRectF, mTargetScale); @@ -218,14 +218,14 @@ public class ClipAnimationHelper { return mCurrentRectWithInsets; } - private void applyParams(@Nullable SyncRtSurfaceTransactionApplier syncTransactionApplier, + private void applyParams(@Nullable SyncRtSurfaceTransactionApplierCompat syncTransactionApplier, SurfaceParams[] params) { if (syncTransactionApplier != null) { syncTransactionApplier.scheduleApply(params); } else { TransactionCompat t = new TransactionCompat(); for (SurfaceParams param : params) { - SyncRtSurfaceTransactionApplier.applyParams(t, param); + SyncRtSurfaceTransactionApplierCompat.applyParams(t, param); } t.setEarlyWakeup(); t.apply(); diff --git a/quickstep/tests/src/com/android/quickstep/StartLauncherViaGestureTests.java b/quickstep/tests/src/com/android/quickstep/StartLauncherViaGestureTests.java new file mode 100644 index 0000000000..52f3c538c6 --- /dev/null +++ b/quickstep/tests/src/com/android/quickstep/StartLauncherViaGestureTests.java @@ -0,0 +1,79 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.quickstep; + +import static com.android.launcher3.util.RaceConditionTracker.enterEvt; +import static com.android.launcher3.util.RaceConditionTracker.exitEvt; + +import android.content.Intent; + +import androidx.test.filters.LargeTest; +import androidx.test.runner.AndroidJUnit4; + +import com.android.launcher3.Launcher; +import com.android.launcher3.util.RaceConditionReproducer; +import com.android.quickstep.QuickStepOnOffRule.Mode; +import com.android.quickstep.QuickStepOnOffRule.QuickstepOnOff; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +@LargeTest +@RunWith(AndroidJUnit4.class) +public class StartLauncherViaGestureTests extends AbstractQuickStepTest { + @Override + @Before + public void setUp() throws Exception { + super.setUp(); + // Start an activity where the gestures start. + startAppFast(resolveSystemApp(Intent.CATEGORY_APP_CALCULATOR)); + } + + private void runTest(String... eventSequence) { + final RaceConditionReproducer eventProcessor = new RaceConditionReproducer(eventSequence); + + // Destroy Launcher activity. + closeLauncherActivity(); + + // The test action. + eventProcessor.startIteration(); + mLauncher.pressHome(); + eventProcessor.finishIteration(); + } + + @Test + @QuickstepOnOff(mode = Mode.ON) + public void testPressHome() { + runTest(enterEvt(Launcher.ON_CREATE_EVT), + exitEvt(Launcher.ON_CREATE_EVT), + enterEvt(OtherActivityTouchConsumer.DOWN_EVT), + exitEvt(OtherActivityTouchConsumer.DOWN_EVT)); + + runTest(enterEvt(OtherActivityTouchConsumer.DOWN_EVT), + exitEvt(OtherActivityTouchConsumer.DOWN_EVT), + enterEvt(Launcher.ON_CREATE_EVT), + exitEvt(Launcher.ON_CREATE_EVT)); + } + + @Test + @QuickstepOnOff(mode = Mode.ON) + public void testSwipeToOverview() { + closeLauncherActivity(); + mLauncher.getBackground().switchToOverview(); + } +} \ No newline at end of file diff --git a/quickstep/tests/src/com/android/quickstep/TaplTests.java b/quickstep/tests/src/com/android/quickstep/TaplTests.java new file mode 100644 index 0000000000..6a1123e02e --- /dev/null +++ b/quickstep/tests/src/com/android/quickstep/TaplTests.java @@ -0,0 +1,462 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.quickstep; + +import static androidx.test.InstrumentationRegistry.getInstrumentation; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; + +import android.content.Intent; +import android.os.RemoteException; +import android.util.Log; + +import androidx.test.filters.LargeTest; +import androidx.test.runner.AndroidJUnit4; +import androidx.test.uiautomator.By; +import androidx.test.uiautomator.Until; + +import com.android.launcher3.Launcher; +import com.android.launcher3.LauncherState; +import com.android.launcher3.tapl.AllApps; +import com.android.launcher3.tapl.AllAppsFromOverview; +import com.android.launcher3.tapl.AppIcon; +import com.android.launcher3.tapl.Background; +import com.android.launcher3.tapl.Overview; +import com.android.launcher3.tapl.OverviewTask; +import com.android.launcher3.tapl.TestHelpers; +import com.android.launcher3.tapl.Widgets; +import com.android.launcher3.tapl.Workspace; +import com.android.launcher3.views.OptionsPopupView; +import com.android.launcher3.widget.WidgetsFullSheet; +import com.android.launcher3.widget.WidgetsRecyclerView; +import com.android.quickstep.QuickStepOnOffRule.QuickstepOnOff; +import com.android.quickstep.views.RecentsView; + +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TestWatcher; +import org.junit.runner.Description; +import org.junit.runner.RunWith; + +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.IOException; + +@LargeTest +@RunWith(AndroidJUnit4.class) +public class TaplTests extends AbstractQuickStepTest { + private static final String TAG = "TaplTests"; + + private static int sScreenshotCount = 0; + + @Rule + public TestWatcher mFailureWatcher = new TestWatcher() { + private void dumpViewHierarchy() { + final ByteArrayOutputStream stream = new ByteArrayOutputStream(); + try { + mDevice.dumpWindowHierarchy(stream); + stream.flush(); + stream.close(); + for (String line : stream.toString().split("\\r?\\n")) { + Log.e(TaplTests.TAG, line.trim()); + } + } catch (IOException e) { + Log.e(TaplTests.TAG, "error dumping XML to logcat", e); + } + } + + @Override + protected void failed(Throwable e, Description description) { + if (mDevice == null) return; + final String pathname = getInstrumentation().getTargetContext(). + getFilesDir().getPath() + "/TaplTestScreenshot" + sScreenshotCount++ + ".png"; + Log.e(TaplTests.TAG, "Failed test " + description.getMethodName() + + ", screenshot will be saved to " + pathname + + ", track trace is below, UI object dump is further below:\n" + + Log.getStackTraceString(e)); + dumpViewHierarchy(); + mDevice.takeScreenshot(new File(pathname)); + } + }; + + @Before + public void setUp() throws Exception { + super.setUp(); + + clearLauncherData(); + + mDevice.pressHome(); + waitForState("Launcher internal state didn't switch to Home", LauncherState.NORMAL); + waitForResumed("Launcher internal state is still Background"); + } + + private boolean isInState(LauncherState state) { + if (!TestHelpers.isInLauncherProcess()) return true; + return getFromLauncher(launcher -> launcher.getStateManager().getState() == state); + } + + // Please don't add negative test cases for methods that fail only after a long wait. + private void expectFail(String message, Runnable action) { + boolean failed = false; + try { + action.run(); + } catch (AssertionError e) { + failed = true; + } + assertTrue(message, failed); + } + + private boolean isWorkspaceScrollable(Launcher launcher) { + return launcher.getWorkspace().getPageCount() > 1; + } + + private boolean isInBackground(Launcher launcher) { + return !launcher.hasBeenResumed(); + } + + private void startTestApps() throws Exception { + startAppFast(resolveSystemApp(Intent.CATEGORY_APP_MESSAGING)); + startAppFast(resolveSystemApp(Intent.CATEGORY_APP_CALCULATOR)); + startAppFast(resolveSystemApp(Intent.CATEGORY_APP_CONTACTS)); + + executeOnLauncher(launcher -> assertTrue( + "Launcher activity is the top activity; expecting another activity to be the top " + + "one", + isInBackground(launcher))); + } + + private int getCurrentWorkspacePage(Launcher launcher) { + return launcher.getWorkspace().getCurrentPage(); + } + + private WidgetsRecyclerView getWidgetsView(Launcher launcher) { + return WidgetsFullSheet.getWidgetsView(launcher); + } + + @Test + public void testDevicePressMenu() throws Exception { + mDevice.pressMenu(); + mDevice.waitForIdle(); + executeOnLauncher( + launcher -> assertTrue("Launcher internal state didn't switch to Showing Menu", + OptionsPopupView.getOptionsPopup(launcher) != null)); + } + + @Test + @PortraitLandscape + public void testPressRecentAppsLauncherAndGetOverview() throws RemoteException { + mDevice.pressRecentApps(); + waitForState("Launcher internal state didn't switch to Overview", LauncherState.OVERVIEW); + + assertNotNull("getOverview() returned null", mLauncher.getOverview()); + } + + private void runAllAppsTest(AllApps allApps) throws Exception { + assertNotNull("allApps parameter is null", allApps); + + assertTrue("Launcher internal state is not All Apps", isInState(LauncherState.ALL_APPS)); + + // Test flinging forward and backward. + executeOnLauncher(launcher -> assertEquals("All Apps started in already scrolled state", 0, + getAllAppsScroll(launcher))); + + allApps.flingForward(); + assertTrue("Launcher internal state is not All Apps", isInState(LauncherState.ALL_APPS)); + final Integer flingForwardY = getFromLauncher(launcher -> getAllAppsScroll(launcher)); + executeOnLauncher( + launcher -> assertTrue("flingForward() didn't scroll App Apps", flingForwardY > 0)); + + allApps.flingBackward(); + assertTrue("Launcher internal state is not All Apps", isInState(LauncherState.ALL_APPS)); + final Integer flingBackwardY = getFromLauncher(launcher -> getAllAppsScroll(launcher)); + executeOnLauncher(launcher -> assertTrue("flingBackward() didn't scroll App Apps", + flingBackwardY < flingForwardY)); + + // Test scrolling down to YouTube. + assertNotNull("All apps: can't fine YouTube", allApps.getAppIcon("YouTube")); + // Test scrolling up to Camera. + assertNotNull("All apps: can't fine Camera", allApps.getAppIcon("Camera")); + // Test failing to find a non-existing app. + final AllApps allAppsFinal = allApps; + expectFail("All apps: could find a non-existing app", + () -> allAppsFinal.getAppIcon("NO APP")); + + assertTrue("Launcher internal state is not All Apps", isInState(LauncherState.ALL_APPS)); + } + + private int getAllAppsScroll(Launcher launcher) { + return launcher.getAppsView().getActiveRecyclerView().getCurrentScrollY(); + } + + @Test + @PortraitLandscape + public void testAllAppsFromHome() throws Exception { + // Test opening all apps + assertNotNull("switchToAllApps() returned null", + mLauncher.getWorkspace().switchToAllApps()); + + runAllAppsTest(mLauncher.getAllApps()); + + // Testing pressHome. + assertTrue("Launcher internal state is not All Apps", isInState(LauncherState.ALL_APPS)); + assertNotNull("pressHome returned null", mLauncher.pressHome()); + assertTrue("Launcher internal state is not Home", isInState(LauncherState.NORMAL)); + assertNotNull("getHome returned null", mLauncher.getWorkspace()); + } + + @Test + @QuickstepOnOff + @PortraitLandscape + public void testWorkspaceSwitchToAllApps() { + assertNotNull("switchToAllApps() returned null", + mLauncher.getWorkspace().switchToAllApps()); + assertTrue("Launcher internal state is not All Apps", isInState(LauncherState.ALL_APPS)); + } + + @Test + public void testAllAppsFromOverview() throws Exception { + // Test opening all apps from Overview. + assertNotNull("switchToAllApps() returned null", + mLauncher.getWorkspace().switchToOverview().switchToAllApps()); + + runAllAppsTest(mLauncher.getAllAppsFromOverview()); + } + + @Test + public void testWorkspace() throws Exception { + final Workspace workspace = mLauncher.getWorkspace(); + + // Test that ensureWorkspaceIsScrollable adds a page by dragging an icon there. + executeOnLauncher(launcher -> assertFalse("Initial workspace state is scrollable", + isWorkspaceScrollable(launcher))); + assertNull("Messages app was found on empty workspace", + workspace.tryGetWorkspaceAppIcon("Messages")); + + workspace.ensureWorkspaceIsScrollable(); + + executeOnLauncher( + launcher -> assertEquals("Ensuring workspace scrollable didn't switch to page #1", + 1, getCurrentWorkspacePage(launcher))); + executeOnLauncher( + launcher -> assertTrue("ensureScrollable didn't make workspace scrollable", + isWorkspaceScrollable(launcher))); + assertNotNull("ensureScrollable didn't add Messages app", + workspace.tryGetWorkspaceAppIcon("Messages")); + + // Test flinging workspace. + workspace.flingBackward(); + assertTrue("Launcher internal state is not Home", isInState(LauncherState.NORMAL)); + executeOnLauncher( + launcher -> assertEquals("Flinging back didn't switch workspace to page #0", + 0, getCurrentWorkspacePage(launcher))); + + workspace.flingForward(); + executeOnLauncher( + launcher -> assertEquals("Flinging forward didn't switch workspace to page #1", + 1, getCurrentWorkspacePage(launcher))); + assertTrue("Launcher internal state is not Home", isInState(LauncherState.NORMAL)); + + // Test starting a workspace app. + final AppIcon app = workspace.tryGetWorkspaceAppIcon("Messages"); + assertNotNull("No Messages app in workspace", app); + assertNotNull("AppIcon.launch returned null", + app.launch(resolveSystemApp(Intent.CATEGORY_APP_MESSAGING))); + executeOnLauncher(launcher -> assertTrue( + "Launcher activity is the top activity; expecting another activity to be the top " + + "one", + isInBackground(launcher))); + } + + @Test + @PortraitLandscape + public void testOverview() throws Exception { + startTestApps(); + Overview overview = mLauncher.pressHome().switchToOverview(); + assertTrue("Launcher internal state didn't switch to Overview", + isInState(LauncherState.OVERVIEW)); + executeOnLauncher( + launcher -> assertTrue("Don't have at least 3 tasks", getTaskCount(launcher) >= 3)); + + // Test flinging forward and backward. + executeOnLauncher(launcher -> assertEquals("Current task in Overview is not 0", + 0, getCurrentOverviewPage(launcher))); + + overview.flingForward(); + assertTrue("Launcher internal state is not Overview", isInState(LauncherState.OVERVIEW)); + final Integer currentTaskAfterFlingForward = getFromLauncher( + launcher -> getCurrentOverviewPage(launcher)); + executeOnLauncher(launcher -> assertTrue("Current task in Overview is still 0", + currentTaskAfterFlingForward > 0)); + + overview.flingBackward(); + assertTrue("Launcher internal state is not Overview", isInState(LauncherState.OVERVIEW)); + executeOnLauncher(launcher -> assertTrue("Flinging back in Overview did nothing", + getCurrentOverviewPage(launcher) < currentTaskAfterFlingForward)); + + // Test opening a task. + OverviewTask task = mLauncher.pressHome().switchToOverview().getCurrentTask(); + assertNotNull("overview.getCurrentTask() returned null (1)", task); + assertNotNull("OverviewTask.open returned null", task.open()); + assertTrue("Contacts app didn't open from Overview", mDevice.wait(Until.hasObject( + By.pkg(resolveSystemApp(Intent.CATEGORY_APP_CONTACTS)).depth(0)), + LONG_WAIT_TIME_MS)); + executeOnLauncher(launcher -> assertTrue( + "Launcher activity is the top activity; expecting another activity to be the top " + + "one", + isInBackground(launcher))); + + // Test dismissing a task. + overview = mLauncher.pressHome().switchToOverview(); + assertTrue("Launcher internal state didn't switch to Overview", + isInState(LauncherState.OVERVIEW)); + final Integer numTasks = getFromLauncher(launcher -> getTaskCount(launcher)); + task = overview.getCurrentTask(); + assertNotNull("overview.getCurrentTask() returned null (2)", task); + task.dismiss(); + executeOnLauncher( + launcher -> assertEquals("Dismissing a task didn't remove 1 task from Overview", + numTasks - 1, getTaskCount(launcher))); + + if (!TestHelpers.isInLauncherProcess() || + getFromLauncher(launcher -> !launcher.getDeviceProfile().isLandscape)) { + // Test switching to all apps and back. + final AllAppsFromOverview allApps = overview.switchToAllApps(); + assertNotNull("overview.switchToAllApps() returned null (1)", allApps); + assertTrue("Launcher internal state is not All Apps (1)", + isInState(LauncherState.ALL_APPS)); + + overview = allApps.switchBackToOverview(); + assertNotNull("allApps.switchBackToOverview() returned null", overview); + assertTrue("Launcher internal state didn't switch to Overview", + isInState(LauncherState.OVERVIEW)); + + // Test UIDevice.pressBack() + overview.switchToAllApps(); + assertNotNull("overview.switchToAllApps() returned null (2)", allApps); + assertTrue("Launcher internal state is not All Apps (2)", + isInState(LauncherState.ALL_APPS)); + mDevice.pressBack(); + mLauncher.getOverview(); + } + + // Test UIDevice.pressHome, once we are in AllApps. + mDevice.pressHome(); + waitForState("Launcher internal state didn't switch to Home", LauncherState.NORMAL); + } + + private int getCurrentOverviewPage(Launcher launcher) { + return launcher.getOverviewPanel().getCurrentPage(); + } + + private int getTaskCount(Launcher launcher) { + return launcher.getOverviewPanel().getTaskViewCount(); + } + + private void runIconLaunchFromAllAppsTest(AllApps allApps) throws Exception { + final AppIcon app = allApps.getAppIcon("Calculator"); + assertNotNull("AppIcon.launch returned null", app.launch( + resolveSystemApp(Intent.CATEGORY_APP_CALCULATOR))); + executeOnLauncher(launcher -> assertTrue( + "Launcher activity is the top activity; expecting another activity to be the top " + + "one", + isInBackground(launcher))); + } + + @Test + @PortraitLandscape + public void testAppIconLaunchFromAllAppsFromHome() throws Exception { + final AllApps allApps = mLauncher.getWorkspace().switchToAllApps(); + assertTrue("Launcher internal state is not All Apps", isInState(LauncherState.ALL_APPS)); + + runIconLaunchFromAllAppsTest(allApps); + } + + @Test + public void testAppIconLaunchFromAllAppsFromOverview() throws Exception { + final AllApps allApps = + mLauncher.getWorkspace().switchToOverview().switchToAllApps(); + assertTrue("Launcher internal state is not All Apps", isInState(LauncherState.ALL_APPS)); + + runIconLaunchFromAllAppsTest(allApps); + } + + @Test + @PortraitLandscape + public void testWidgets() throws Exception { + // Test opening widgets. + executeOnLauncher(launcher -> + assertTrue("Widgets is initially opened", getWidgetsView(launcher) == null)); + Widgets widgets = mLauncher.getWorkspace().openAllWidgets(); + assertNotNull("openAllWidgets() returned null", widgets); + widgets = mLauncher.getAllWidgets(); + assertNotNull("getAllWidgets() returned null", widgets); + executeOnLauncher(launcher -> + assertTrue("Widgets is not shown", getWidgetsView(launcher).isShown())); + executeOnLauncher(launcher -> assertEquals("Widgets is scrolled upon opening", + 0, getWidgetsScroll(launcher))); + + // Test flinging widgets. + widgets.flingForward(); + Integer flingForwardY = getFromLauncher(launcher -> getWidgetsScroll(launcher)); + executeOnLauncher(launcher -> assertTrue("Flinging forward didn't scroll widgets", + flingForwardY > 0)); + + widgets.flingBackward(); + executeOnLauncher(launcher -> assertTrue("Flinging backward didn't scroll widgets", + getWidgetsScroll(launcher) < flingForwardY)); + + mDevice.pressHome(); + waitForLauncherCondition("Widgets were not closed", + launcher -> getWidgetsView(launcher) == null); + } + + private int getWidgetsScroll(Launcher launcher) { + return getWidgetsView(launcher).getCurrentScrollY(); + } + + @Test + @QuickstepOnOff + @PortraitLandscape + public void testSwitchToOverview() throws Exception { + assertNotNull("Workspace.switchToOverview() returned null", + mLauncher.pressHome().switchToOverview()); + assertTrue("Launcher internal state didn't switch to Overview", + isInState(LauncherState.OVERVIEW)); + } + + @Test + @QuickstepOnOff + @PortraitLandscape + public void testBackground() throws Exception { + startAppFast(resolveSystemApp(Intent.CATEGORY_APP_CALCULATOR)); + final Background background = mLauncher.getBackground(); + assertNotNull("Launcher.getBackground() returned null", background); + executeOnLauncher(launcher -> assertTrue( + "Launcher activity is the top activity; expecting another activity to be the top " + + "one", + isInBackground(launcher))); + + assertNotNull("Background.switchToOverview() returned null", background.switchToOverview()); + assertTrue("Launcher internal state didn't switch to Overview", + isInState(LauncherState.OVERVIEW)); + } +} diff --git a/res/raw/downgrade_schema.json b/res/raw/downgrade_schema.json index 3c1b64f92a..8f1780e5f7 100644 --- a/res/raw/downgrade_schema.json +++ b/res/raw/downgrade_schema.json @@ -2,8 +2,12 @@ // Note: Comments are not supported in JSON schema, but android parser is lenient. // Maximum DB version supported by this schema - "version" : 27, + "version" : 28, + "downgrade_to_27" : [ + "CREATE TABLE workspaceScreens (_id INTEGER PRIMARY KEY,screenRank INTEGER,modified INTEGER NOT NULL DEFAULT 0)", + "insert into workspaceScreens (_id, screenRank) select screen as _id, screen as screenRank from favorites where container = -100 group by screen order by screen" + ], // Downgrade from 27 to 26. Empty array indicates, the DB is compatible "downgrade_to_26" : [], "downgrade_to_25" : [], diff --git a/res/values-af/strings.xml b/res/values-af/strings.xml index b93c3701eb..3f505462ee 100644 --- a/res/values-af/strings.xml +++ b/res/values-af/strings.xml @@ -39,8 +39,7 @@ "Laai tans programme …" "Kon geen programme kry wat by \"%1$s\" pas nie" "Soek meer programme" - - + "Program" "Kennisgewings" "Raak en hou om \'n kortpad op te tel." "Dubbeltik en hou om \'n kortpad op te tel of gebruik gepasmaakte handelinge." @@ -66,7 +65,10 @@ "Dit is \'n stelselprogram en kan nie gedeïnstalleer word nie." "Naamlose vouer" "Het %1$s gedeaktiveer" - + + %1$s, het %2$d kennisgewings + %1$s, het %2$d kennisgewing + "Bladsy %1$d van %2$d" "Tuisskerm %1$d van %2$d" "Nuwe tuisskermbladsy" @@ -82,17 +84,13 @@ "Gedeaktiveer deur jou administrateur" "Laat toe dat tuisskerm gedraai word" "Wanneer foon gedraai word" - - - - - - + "Kennisgewingkolle" + "Aan" + "Af" "Kennisgewingtoegang word benodig" "Skakel programkennisgewings vir %1$s aan om kennisgewingkolle te sien" "Verander instellings" - - + "Wys kennisgewingkolle" "Voeg ikoon by tuisskerm" "Vir nuwe programme" "Verander ikoon se vorm" diff --git a/res/values-am/strings.xml b/res/values-am/strings.xml index d65fa673d7..9f8ee87ca5 100644 --- a/res/values-am/strings.xml +++ b/res/values-am/strings.xml @@ -39,8 +39,7 @@ "መተግበሪያዎችን በመጫን ላይ…" "ከ«%1$s» ጋር የሚዛመዱ ምንም መተግበሪያዎች አልተገኙም" "ተጨማሪ መተግበሪያዎች ይፈልጉ" - - + "መተግበሪያ" "ማሳወቂያዎች" "አንድ አቋራጭ ለመውሰድ ነክተው ይያዙ።" "አንድ አቋራጭ ለመውሰድ ወይም ብጁ እርምጃዎችን ለመጠቀም ሁለቴ መታ አድርገው ይያዙ።" @@ -66,7 +65,10 @@ "ይህ የስርዓት መተግበሪያ ነው እና ማራገፍ አይቻልም።" "ስም-አልባ አቃፊ" "%1$s ተሰናክሏል" - + + %1$s%2$d ማሳወቂያ አለው + %1$s%2$d ማሳወቂያ አለው + "ገጽ %1$d ከ%2$d" "መነሻ ማያ ገጽ %1$d ከ%2$d" "አዲስ የመነሻ ማያ ገጽ" @@ -82,17 +84,13 @@ "በእርስዎ አስተዳዳሪ የተሰናከለ" "የመነሻ ማያ ገጽ ማሽከርከርን ይፍቀዱ" "ስልኩ ሲዞር" - - - - - - + "የማሳወቂያ ነጥቦች" + "አብራ" + "ጠፍቷል" "የማሳወቂያ መዳረሻ ያስፈልጋል" "የማሳወቂያ ነጥቦችን ለማሳየት የመተግብሪያ ማሳወቂያዎችን ለ%1$s ያብሩ" "ቅንብሮችን ቀይር" - - + "የማሳወቂያ ነጥቦችን አሳይ" "አዶ ወደ የመነሻ ማያ ገጽ አክል" "ለአዲስ መተግበሪያዎች" "የአዶ ቅርፅ ለውጥ" diff --git a/res/values-ar/strings.xml b/res/values-ar/strings.xml index 988f9b9e29..97e9784d2f 100644 --- a/res/values-ar/strings.xml +++ b/res/values-ar/strings.xml @@ -39,8 +39,7 @@ "جارٍ تحميل التطبيقات…" "لم يتم العثور على أي تطبيقات تتطابق مع \"%1$s\"" "البحث عن مزيد من التطبيقات" - - + "تطبيق" "الإشعارات" "انقر مع الاستمرار لاختيار اختصار." "يمكنك النقر مرّتين مع الاستمرار لاختيار اختصار أو استخدام الإجراءات المخصصة." @@ -66,7 +65,14 @@ "هذا تطبيق نظام وتتعذر إزالته." "مجلد بدون اسم" "تم إيقاف %1$s" - + + يتضمن تطبيق %1$s %2$d إشعار.​ + يتضمن تطبيق %1$s إشعارين (%2$d). + يتضمن تطبيق %1$s %2$d إشعارات.​ + يتضمن تطبيق %1$s %2$d إشعارًا. + يتضمن تطبيق %1$s %2$d إشعار. + يتضمن تطبيق %1$s إشعارًا واحدًا (%2$d). + "‏الصفحة %1$d من %2$d" "‏الشاشة الرئيسية %1$d من %2$d" "صفحة الشاشة الرئيسية الجديدة" @@ -82,17 +88,13 @@ "أوقف المشرف هذه الميزة" "السماح بتدوير الشاشة الرئيسية" "عند تدوير الهاتف" - - - - - - + "نقاط الإشعارات" + "مفعّل" + "غير مفعّل" "يلزم تفعيل الوصول إلى الإشعارات" "لعرض نقاط الإشعارات، يجب تشغيل إشعارات التطبيق في %1$s" "تغيير الإعدادات" - - + "عرض نقاط الإشعارات" "إضافة رمز إلى الشاشة الرئيسية" "للتطبيقات الجديدة" "تغيير شكل الرمز" diff --git a/res/values-as/strings.xml b/res/values-as/strings.xml index 149a19d639..e4293cfe42 100644 --- a/res/values-as/strings.xml +++ b/res/values-as/strings.xml @@ -39,8 +39,7 @@ "এপসমূহ ল’ড কৰি থকা হৈছে…" "\"%1$s\"ৰ সৈতে মিলা কোনো এপ্ বিচাৰি পোৱা নগ\'ল" "আৰু অধিক এপবোৰ সন্ধান কৰক" - - + "এপ্" "জাননীসমূহ" "কোনো শ্বৰ্টকাট বাছনি কৰিবলৈ স্পৰ্শ কৰি হেঁচি ধৰক।" "কোনো শ্বৰ্টকাট বাছনি কৰিবলৈ দুবাৰ টিপি হেঁচি ধৰক, বা নিজৰ উপযোগিতা অনুসৰি সৃষ্টি কৰা কাৰ্যসমূহ ব্যৱহাৰ কৰক।" @@ -66,7 +65,10 @@ "এইটো এটা ছিষ্টেম এপ আৰু ইয়াক আনইনষ্টল কৰিব নোৱৰি" "নামবিহীন ফ\'ল্ডাৰ" "%1$s অক্ষম কৰা হ\'ল" - + + %1$s%2$dটা জাননী আছে + %1$s%2$dটা জাননী আছে + "%2$dৰ %1$d পৃষ্ঠা" "গৃহ স্ক্ৰীণ %2$dৰ %1$d" "গৃহ স্ক্ৰীণৰ নতুন পৃষ্ঠা" @@ -82,17 +84,13 @@ "আপোনাৰ প্ৰশাসকে অক্ষম কৰি ৰাখিছে" "গৃহ স্ক্ৰীণ ঘূৰোৱাৰ অনুমতি দিয়ক" "ফ\'নটো যেতিয়া ঘূৰোৱা হয়" - - - - - - + "জাননী বিন্দু" + "অন আছে" + "অফ আছে" "জাননী চাবলৈ অনুমতিৰ প্ৰয়োজন" "জাননী সম্পৰ্কীয় বিন্দুবোৰ দেখুৱাবলৈ %1$sৰ বাবে এপৰ জাননীসমূহ অন কৰক" "ছেটিংসমূহ সলনি কৰক" - - + "জাননী বিন্দু দেখুৱাওক" "গৃহ স্ক্ৰীণত আইকনটো যোগ কৰক" "নতুন এপসমূহৰ বাবে" "আইকনৰ আকৃতি সলনি কৰক" diff --git a/res/values-az/strings.xml b/res/values-az/strings.xml index 2a8f046953..5381114d6c 100644 --- a/res/values-az/strings.xml +++ b/res/values-az/strings.xml @@ -39,8 +39,7 @@ "Tətbiqlər yüklənir…" "%1$s sorğusuna uyğun tətbiq tapılmadı" "Daha çox tətbiq üçün axtarış edin" - - + "Tətbiq" "Bildirişlər" "Qısayolu seçmək üçün basıb saxlayın." "Qısayolu seçmək üçün iki dəfə basıb saxlayın və ya fərdi əməliyyatlardan istifadə edin." @@ -66,7 +65,10 @@ "Bu sistem tətbiqi olduğu üçün sistemdən silinə bilməz." "Adsız Qovluq" "%1$s deaktiv edildi" - + + %1$s tətbiqində %2$d bildiriş var + %1$s tətbiqində %2$d bildiriş var + "Səhifə %1$d of %2$d" "Əsas Səhifə ekranı %1$d of %2$d" "Yeni əsas ekran səhifəsi" @@ -82,17 +84,13 @@ "Admininiz tərəfindən deaktiv edilib" "Əsas ekranın firlanmağına icazə verin" "Telefon çevrilən zaman" - - - - - - + "Bildiriş nöqtələri" + "Aktiv" + "Deaktiv" "Bildiriş girişi tələb edilir" "Bildiriş Nöqtələrini göstərmək üçün %1$s bildirişlərini aktiv edin" "Ayarları dəyişin" - - + "Bildiriş nöqtələrini göstərin" "Əsas ekrana ikona əlavə edin" "Yeni tətbiqlər üçün" "İkona formasını dəyişin" diff --git a/res/values-b+sr+Latn/strings.xml b/res/values-b+sr+Latn/strings.xml index c89330e773..568a0fbc20 100644 --- a/res/values-b+sr+Latn/strings.xml +++ b/res/values-b+sr+Latn/strings.xml @@ -39,8 +39,7 @@ "Aplikacije se učitavaju…" "Nije pronađena nijedna aplikacija za „%1$s“" "Pretraži još aplikacija" - - + "Aplikacija" "Obaveštenja" "Dodirnite i zadržite da biste izabrali prečicu." "Dvaput dodirnite i zadržite da biste izabrali prečicu ili koristite prilagođene radnje." @@ -66,7 +65,11 @@ "Ovo je sistemska aplikacija i ne može da se deinstalira." "Neimenovani direktorijum" "Aplikacija %1$s je onemogućena" - + + %1$s, ima %2$d obaveštenje + %1$s, ima %2$d obaveštenja + %1$s, ima %2$d obaveštenja + "%1$d. stranica od %2$d" "%1$d. početni ekran od %2$d" "Nova stranica početnog ekrana" @@ -82,17 +85,13 @@ "Administrator je onemogućio" "Dozvoli rotaciju početnog ekrana" "Kada se telefon rotira" - - - - - - + "Tačke za obaveštenja" + "Uključeno" + "Isključeno" "Potreban je pristup za obaveštenja" "Da biste prikazali tačke za obaveštenja, uključite obaveštenja za aplikaciju %1$s" "Promenite podešavanja" - - + "Prikazuj tačke za obaveštenja" "Dodaj ikonu na početni ekran" "Za nove aplikacije" "Promenite oblik ikona" diff --git a/res/values-be/strings.xml b/res/values-be/strings.xml index 39db29ad37..2f5fd30054 100644 --- a/res/values-be/strings.xml +++ b/res/values-be/strings.xml @@ -39,8 +39,7 @@ "Праграмы загружаюцца…" "Праграм, якія адпавядаюць запыту \"%1$s\", не знойдзена" "Шукаць іншыя праграмы" - - + "Праграма" "Апавяшчэнні" "Дакраніцеся і ўтрымлiвайце ярлык, каб дадаць яго." "Дакраніцеся двойчы і ўтрымлівайце, каб выбраць ярлык або выкарыстоўваць спецыяльныя дзеянні." @@ -66,7 +65,12 @@ "Гэта сістэмная праграма, яе нельга выдаліць." "Папка без назвы" "%1$s адключана" - + + %1$s, мае %2$d апавяшчэнне + %1$s, мае %2$d апавяшчэнні + %1$s, мае %2$d апавяшчэнняў + %1$s, мае %2$d апавяшчэння + "Старонка %1$d з %2$d" "Галоўны экран %1$d з %2$d" "Новая старонка галоўнага экрана" @@ -82,17 +86,13 @@ "Адключаная адміністратарам" "Дазволіць паварот галоўнага экрана" "Пры павароце тэлефона" - - - - - - + "Значкі апавяшчэнняў" + "Укл." + "Выкл." "Патрабуецца доступ да апавяшчэнняў" "Каб паказваліся значкі апавяшчэнняў, уключыце апавяшчэнні праграм для %1$s" "Змяніць налады" - - + "Паказваць значкі апавяшчэнняў" "Дадаць значок на Галоўны экран" "Для новых праграм" "Змяніць форму значка" diff --git a/res/values-bg/strings.xml b/res/values-bg/strings.xml index 9083f4ce0d..286a6e886a 100644 --- a/res/values-bg/strings.xml +++ b/res/values-bg/strings.xml @@ -39,8 +39,7 @@ "Приложенията се зареждат…" "Няма намерени приложения, съответстващи на „%1$s“" "Търсене на още приложения" - - + "Приложение" "Известия" "Докоснете и задръжте за избор на пряк път." "Докоснете двукратно и задръжте за избор на пряк път или използвайте персонализирани действия." @@ -66,7 +65,10 @@ "Това е системно приложение и не може да се деинсталира." "Папка без име" "Деактивирахте %1$s" - + + %1$s – има %2$d известия + %1$s – има %2$d известие + "Страница %1$d от %2$d" "Начален екран %1$d от %2$d" "Нова страница на началния екран" @@ -82,17 +84,13 @@ "Деактивирано от администратора ви" "Разрешаване на завъртането на началния екран" "При завъртане на телефона" - - - - - - + "Точки за известия" + "Вкл." + "Изкл." "Необходим е достъп до известията" "За да се показват точки за известия, включете известията за приложението %1$s" "Промяна на настройките" - - + "Показване на точките за известия" "Добавяне на икона към началния екран" "За нови приложения" "Промяна на формата на иконите" diff --git a/res/values-bn/strings.xml b/res/values-bn/strings.xml index c5adb7b1c2..6356f149d7 100644 --- a/res/values-bn/strings.xml +++ b/res/values-bn/strings.xml @@ -39,8 +39,7 @@ "অ্যাপ লোড হচ্ছে…" "\"%1$s\" এর সাথে মেলে এমন কোনো অ্যাপ পাওয়া যায়নি" "আরও অ্যাপ্লিকেশানের জন্য খুঁজুন" - - + "অ্যাপ" "বিজ্ঞপ্তি" "কোনও শর্টকাট বেছে নিতে টাচ করে ধরে থাকুন।" "কোনও শর্টকাট বেছে নিতে ডবল ট্যাপ করে ধরে থাকুন অথবা কাস্টম অ্যাকশন ব্যবহার করুন।" @@ -66,7 +65,10 @@ "এটি একটি সিস্টেম অ্যাপ্লিকেশান এবং আনইনস্টল করা যাবে না৷" "নামবিহীন ফোল্ডার" "%1$s অক্ষম করা হয়েছে" - + + %1$s, %2$dটি বিজ্ঞপ্তি আছে + %1$s, %2$dটি বিজ্ঞপ্তি আছে + "%2$dটির মধ্যে %1$dটি পৃষ্ঠা" "%2$dটির %1$d নম্বর হোম স্ক্রিন" "নতুন হোম স্ক্রীনের পৃষ্ঠা" @@ -82,17 +84,13 @@ "আপনার প্রশাসক দ্বারা অক্ষম করা হয়েছে" "হোমস্ক্রীন ঘোরানোর অনুমতি দিন" "যখন ফোনটি ঘোরানো হয়" - - - - - - + "বিজ্ঞপ্তি ডট" + "চালু" + "বন্ধ" "বিজ্ঞপ্তিতে অ্যাক্সেস প্রয়োজন" "বিজ্ঞপ্তির ডটগুলি দেখানোর জন্য, %1$s এর অ্যাপ বিজ্ঞপ্তি চালু করুন" "সেটিংস পরিবর্তন করুন" - - + "বিজ্ঞপ্তির ডট দেখুন" "হোম স্ক্রিনে আইকন যোগ করুন" "নতুন অ্যাপ্লিকেশানগুলির জন্যে" "আইকনের আকৃতি পরিবর্তন করুন" diff --git a/res/values-bs/strings.xml b/res/values-bs/strings.xml index 74624cad7f..e98e1cde3a 100644 --- a/res/values-bs/strings.xml +++ b/res/values-bs/strings.xml @@ -39,8 +39,7 @@ "Aplikacije se učitavaju…" "Nije pronađena nijedna aplikacija za upit \"%1$s\"" "Pretraži više aplikacija" - - + "Aplikacija" "Obavještenja" "Dodirnite i držite da uzmete prečicu." "Dodirnite dvaput i držite da uzmete prečicu ili koristite prilagođene akcije." @@ -66,7 +65,11 @@ "Ovo je sistemska aplikacija i ne može se deinstalirati." "Neimenovani folder" "Aplikacija %1$s je onemogućena" - + + Aplikacija %1$s ima%2$d obavještenje​ + Aplikacija %1$s ima%2$d obavještenja​ + Aplikacija %1$s ima%2$d obavještenja​ + "Strana %1$d od %2$d" "Početni ekran %1$d od %2$d" "Nova stranica početnog ekrana" @@ -82,17 +85,13 @@ "Onemogućio vaš administrator" "Dozvoli rotiranje početnog ekrana" "Kada se telefon zarotira" - - - - - - + "Tačke za obavještenja" + "Uključeno" + "Isključeno" "Potreban je pristup obavještenjima" "Za prikaz tačaka obavještenja, uključite obavještenja za aplikacije za aplikaciju %1$s" "Promijeni postavke" - - + "Prikaži tačke za obavještenja" "Dodaj ikonu na početni ekran" "Za nove aplikacije" "Promjena oblika ikona" diff --git a/res/values-ca/strings.xml b/res/values-ca/strings.xml index b34dcb67c2..52cd6a3bbb 100644 --- a/res/values-ca/strings.xml +++ b/res/values-ca/strings.xml @@ -39,8 +39,7 @@ "S\'estan carregant les aplicacions…" "No s\'ha trobat cap aplicació que coincideixi amb \"%1$s\"" "Cerca més aplicacions" - - + "Aplicació" "Notificacions" "Mantén premuda una drecera per seleccionar-la." "Fes doble toc i mantén premut per seleccionar una drecera o per utilitzar accions personalitzades." @@ -66,7 +65,10 @@ "Aquesta aplicació és una aplicació del sistema i no es pot desinstal·lar." "Carpeta sense nom" "S\'ha desactivat %1$s" - + + %1$s​ té %2$d notificacions + %1$s%2$d notificació + "Pàgina %1$d de %2$d" "Pantalla d\'inici %1$d de %2$d" "Pàgina de la pantalla d\'inici nova" @@ -82,17 +84,13 @@ "Desactivada per l\'administrador" "Permet la rotació de la pantalla d\'inici" "En girar el telèfon" - - - - - - + "Punts de notificació" + "Activats" + "Desactivats" "Cal que tingui accés a les notificacions" "Per veure els punts de notificació, activa les notificacions de l\'aplicació %1$s" "Canvia la configuració" - - + "Mostra els punts de notificació" "Afegeix la icona a la pantalla d\'inici" "Per a les aplicacions noves" "Canvia la forma de les icones" diff --git a/res/values-cs/strings.xml b/res/values-cs/strings.xml index 187b60b7f6..59dabbc403 100644 --- a/res/values-cs/strings.xml +++ b/res/values-cs/strings.xml @@ -39,8 +39,7 @@ "Načítání aplikací…" "Dotazu „%1$s“ neodpovídají žádné aplikace" "Vyhledat další aplikace" - - + "Aplikace" "Oznámení" "Zkratku vyberete podržením." "Dvojitým klepnutím a podržením vyberte zkratku, případně použijte vlastní akce." @@ -66,7 +65,12 @@ "Toto je systémová aplikace a nelze ji odinstalovat." "Složka bez názvu" "Aplikace %1$s je zakázána" - + + Aplikace %1$s%2$d oznámení + Aplikace %1$s%2$d oznámení + Aplikace %1$s%2$d oznámení + Aplikace %1$s%2$d oznámení + "Strana %1$d z %2$d" "Plocha %1$d z %2$d" "Nová stránka plochy" @@ -82,17 +86,13 @@ "Zakázáno administrátorem" "Povolit otáčení plochy" "Při otočení telefonu" - - - - - - + "Puntíky s oznámením" + "Zapnuto" + "Vypnuto" "Je třeba udělit přístup k oznámením" "Chcete-li zobrazovat puntíky s oznámením, zapněte oznámení z aplikace %1$s" "Změnit nastavení" - - + "Zobrazovat puntíky s oznámením" "Přidat ikonu na plochu" "Pro nové aplikace" "Změnit tvar ikony" diff --git a/res/values-da/strings.xml b/res/values-da/strings.xml index 745b534736..19ffe36673 100644 --- a/res/values-da/strings.xml +++ b/res/values-da/strings.xml @@ -39,8 +39,7 @@ "Indlæser apps…" "Der blev ikke fundet nogen apps, som matcher \"%1$s\"" "Søg efter flere apps" - - + "App" "Underretninger" "Hold en genvej nede for at samle den op." "Tryk to gange, og hold en genvej nede for at samle den op eller bruge tilpassede handlinger." @@ -66,7 +65,10 @@ "Dette er en systemapp, som ikke kan afinstalleres." "Unavngiven mappe" "%1$s er deaktiveret" - + + %1$s, har %2$d underretning + %1$s, har %2$d underretninger + "Side %1$d ud af %2$d" "Startskærm %1$d ud af %2$d" "Ny startskærm" @@ -82,17 +84,13 @@ "Deaktiveret af din administrator" "Tillad rotation af startskærmen" "Når telefonen roteres" - - - - - - + "Underretningscirkler" + "Til" + "Fra" "Kræver adgang til underretninger" "Hvis du vil se underretningscirkler, skal du aktivere appunderretninger for %1$s" "Skift indstillinger" - - + "Vis underretningscirkler" "Føj ikon til startskærmen" "For nye apps" "Skift ikonform" diff --git a/res/values-de/strings.xml b/res/values-de/strings.xml index 33016318fb..4d4c1daf23 100644 --- a/res/values-de/strings.xml +++ b/res/values-de/strings.xml @@ -39,8 +39,7 @@ "Apps werden geladen…" "Keine Apps für \"%1$s\" gefunden" "Weitere Apps suchen" - - + "App" "Benachrichtigungen" "Tippen und halten, um eine Verknüpfung auszuwählen." "Doppeltippen und halten, um eine Verknüpfung auszuwählen oder benutzerdefinierte Aktionen zu nutzen." @@ -66,7 +65,10 @@ "Dies ist eine Systemanwendung, die nicht deinstalliert werden kann." "Unbenannter Ordner" "%1$s deaktiviert" - + + %1$s, hat %2$d Benachrichtigungen + %1$s, hat %2$d Benachrichtigung + "Seite %1$d von %2$d" "Startbildschirm %1$d von %2$d" "Neue Startbildschirmseite" @@ -82,17 +84,13 @@ "Von deinem Administrator deaktiviert" "Drehung des Startbildschirms zulassen" "Bei Drehung des Smartphones" - - - - - - + "App-Benachrichtigungspunkte" + "An" + "Aus" "Benachrichtigungszugriff erforderlich" "Um dir Benachrichtigungspunkte anzeigen zu lassen, aktiviere die Benachrichtigungen für die App \"%1$s\"" "Einstellungen ändern" - - + "App-Benachrichtigungspunkte anzeigen" "Symbol zu Startbildschirm hinzufügen" "Für neue Apps" "Form des Symbols ändern" diff --git a/res/values-el/strings.xml b/res/values-el/strings.xml index 690da09e1d..35aba42a37 100644 --- a/res/values-el/strings.xml +++ b/res/values-el/strings.xml @@ -39,8 +39,7 @@ "Φόρτωση εφαρμογών…" "Δεν βρέθηκαν εφαρμογές αντιστοίχισης για \"%1$s\"" "Αναζήτηση περισσότερων εφαρμογών" - - + "Εφαρμογή" "Ειδοποιήσεις" "Αγγίξτε παρατεταμένα για επιλογή συντόμευσης." "Πατήσετε δύο φορές παρατεταμένα για επιλογή συντόμευσης ή χρήση προσαρμοσμένων ενεργειών." @@ -66,7 +65,10 @@ "Αυτή είναι μια εφαρμογή συστήματος και δεν είναι δυνατή η κατάργηση της εγκατάστασής της." "Φάκελος χωρίς όνομα" "Η εφαρμογή %1$s είναι απενεργοποιημένη" - + + Η εφαρμογή %1$s, έχει %2$d ειδοποιήσεις + Η εφαρμογή %1$s, έχει %2$d ειδοποίηση + "Σελίδα %1$d από %2$d" "Αρχική οθόνη %1$d από %2$d" "Νέα σελίδα αρχικής οθόνης" @@ -82,17 +84,13 @@ "Απενεργοποιήθηκε από τον διαχειριστή σας" "Να επιτρέπεται η περιστροφή της αρχικής οθόνης" "Όταν το τηλέφωνο περιστρέφεται" - - - - - - + "Κουκκίδες ειδοποίησης" + "Ενεργοποίηση" + "Απενεργοποίηση" "Απαιτείται πρόσβαση στις ειδοποιήσεις" "Για να εμφανιστούν οι Κουκκίδες ειδοποίησης, ενεργοποιήστε τις κουκκίδες εφαρμογής για την εφαρμογή %1$s" "Αλλαγή ρυθμίσεων" - - + "Εμφάνιση κουκκίδων ειδοποιήσεων" "Προσθήκη εικονιδίου στην Αρχική οθόνη" "Για νέες εφαρμογές" "Αλλαγή σχήματος εικονιδίου" diff --git a/res/values-en-rAU/strings.xml b/res/values-en-rAU/strings.xml index 7360892f9c..a56fbefa46 100644 --- a/res/values-en-rAU/strings.xml +++ b/res/values-en-rAU/strings.xml @@ -39,8 +39,7 @@ "Loading apps…" "No apps found matching \'%1$s\'" "Search for more apps" - - + "App" "Notifications" "Touch & hold to pick up a shortcut." "Double-tap & hold to pick up a shortcut or use custom actions." @@ -66,7 +65,10 @@ "This is a system app and can\'t be uninstalled." "Unnamed Folder" "Disabled %1$s" - + + %1$s, has %2$d notifications + %1$s, has %2$d notification + "Page %1$d of %2$d" "Home screen %1$d of %2$d" "New home screen page" @@ -82,17 +84,13 @@ "Disabled by your admin" "Allow Homescreen rotation" "When phone is rotated" - - - - - - + "Notification dots" + "On" + "Off" "Notification access needed" "To show Notification Dots, turn on app notifications for %1$s" "Change settings" - - + "Show notification dots" "Add icon to Home screen" "For new apps" "Change icon shape" diff --git a/res/values-en-rGB/strings.xml b/res/values-en-rGB/strings.xml index 7360892f9c..a56fbefa46 100644 --- a/res/values-en-rGB/strings.xml +++ b/res/values-en-rGB/strings.xml @@ -39,8 +39,7 @@ "Loading apps…" "No apps found matching \'%1$s\'" "Search for more apps" - - + "App" "Notifications" "Touch & hold to pick up a shortcut." "Double-tap & hold to pick up a shortcut or use custom actions." @@ -66,7 +65,10 @@ "This is a system app and can\'t be uninstalled." "Unnamed Folder" "Disabled %1$s" - + + %1$s, has %2$d notifications + %1$s, has %2$d notification + "Page %1$d of %2$d" "Home screen %1$d of %2$d" "New home screen page" @@ -82,17 +84,13 @@ "Disabled by your admin" "Allow Homescreen rotation" "When phone is rotated" - - - - - - + "Notification dots" + "On" + "Off" "Notification access needed" "To show Notification Dots, turn on app notifications for %1$s" "Change settings" - - + "Show notification dots" "Add icon to Home screen" "For new apps" "Change icon shape" diff --git a/res/values-en-rIN/strings.xml b/res/values-en-rIN/strings.xml index 7360892f9c..a56fbefa46 100644 --- a/res/values-en-rIN/strings.xml +++ b/res/values-en-rIN/strings.xml @@ -39,8 +39,7 @@ "Loading apps…" "No apps found matching \'%1$s\'" "Search for more apps" - - + "App" "Notifications" "Touch & hold to pick up a shortcut." "Double-tap & hold to pick up a shortcut or use custom actions." @@ -66,7 +65,10 @@ "This is a system app and can\'t be uninstalled." "Unnamed Folder" "Disabled %1$s" - + + %1$s, has %2$d notifications + %1$s, has %2$d notification + "Page %1$d of %2$d" "Home screen %1$d of %2$d" "New home screen page" @@ -82,17 +84,13 @@ "Disabled by your admin" "Allow Homescreen rotation" "When phone is rotated" - - - - - - + "Notification dots" + "On" + "Off" "Notification access needed" "To show Notification Dots, turn on app notifications for %1$s" "Change settings" - - + "Show notification dots" "Add icon to Home screen" "For new apps" "Change icon shape" diff --git a/res/values-es-rUS/strings.xml b/res/values-es-rUS/strings.xml index b1fd75be7f..f88f8fca73 100644 --- a/res/values-es-rUS/strings.xml +++ b/res/values-es-rUS/strings.xml @@ -39,8 +39,7 @@ "Cargando apps…" "No hay apps que coincidan con \"%1$s\"" "Buscar más apps" - - + "App" "Notificaciones" "Mantén presionado para elegir un acceso directo." "Presiona dos veces y mantén presionado para elegir un acceso directo o usar acciones personalizadas." @@ -66,7 +65,10 @@ "Esta es una aplicación del sistema y no se puede desinstalar." "Carpeta sin nombre" "Se inhabilitó %1$s" - + + %1$s tiene %2$d notificaciones + %1$s tiene %2$d notificación + "Página %1$d de %2$d" "Pantalla principal %1$d de %2$d" "Nueva página en la pantalla principal" @@ -82,17 +84,13 @@ "El administrador inhabilitó esta función" "Permitir la rotación de la pantalla principal" "Al girar el teléfono" - - - - - - + "Puntos de notificación" + "Activado" + "Desactivado" "Se necesita acceso a las notificaciones" "Para mostrar los puntos de notificación, activa las notificaciones de la app para %1$s" "Cambiar la configuración" - - + "Mostrar puntos de notificación" "Agregar ícono a la pantalla principal" "Para nuevas apps" "Cambiar forma de los íconos" diff --git a/res/values-es/strings.xml b/res/values-es/strings.xml index 3021c71b31..3f3322c400 100644 --- a/res/values-es/strings.xml +++ b/res/values-es/strings.xml @@ -39,8 +39,7 @@ "Cargando aplicaciones…" "No se han encontrado aplicaciones que contengan \"%1$s\"" "Buscar más aplicaciones" - - + "Aplicación" "Notificaciones" "Mantén pulsado para elegir un acceso directo." "Toca dos veces y mantén pulsado para elegir un acceso directo o utilizar acciones personalizadas." @@ -66,7 +65,10 @@ "Esta aplicación es del sistema y no se puede desinstalar." "Carpeta sin nombre" "Se ha inhabilitado %1$s" - + + %1$s tiene %2$d notificaciones + %1$s tiene %2$d notificación + "Página %1$d de %2$d" "Pantalla de inicio %1$d de %2$d" "Nueva página de pantalla de inicio" @@ -82,17 +84,13 @@ "Inhabilitado por el administrador" "Permitir rotación de la pantalla de inicio" "Al girar el teléfono" - - - - - - + "Burbujas de notificación" + "Activadas" + "Desactivadas" "Se necesita acceso a las notificaciones" "Para mostrar burbujas de notificación, activa las notificaciones de %1$s" "Cambiar ajustes" - - + "Mostrar burbujas de notificación" "Añadir icono a la pantalla de inicio" "Para aplicaciones nuevas" "Cambiar forma de los iconos" diff --git a/res/values-et/strings.xml b/res/values-et/strings.xml index f3c14a36c5..ea04cdda7b 100644 --- a/res/values-et/strings.xml +++ b/res/values-et/strings.xml @@ -39,8 +39,7 @@ "Rakenduste laadimine …" "Päringule „%1$s” ei vastanud ükski rakendus" "Otsi rohkem rakendusi" - - + "Rakendus" "Märguanded" "Otsetee valimiseks puudutage seda pikalt." "Topeltpuudutage ja hoidke otsetee valimiseks või kohandatud toimingute kasutamiseks." @@ -66,7 +65,10 @@ "See on süsteemirakendus ja seda ei saa desinstallida." "Nimetu kaust" "Rakendus %1$s on keelatud" - + + %1$s, %2$d märguannet + %1$s, %2$d märguanne + "Leht %1$d/%2$d" "Avaekraan %1$d/%2$d" "Uus avaekraan" @@ -82,17 +84,13 @@ "Keelas administraator" "Luba avaekraani pööramine" "Kui telefoni pööratakse" - - - - - - + "Märguandetäpid" + "Sees" + "Väljas" "Vaja on juurdepääsu märguannetele" "Märguandetäppide kuvamiseks lülitage sisse rakenduse %1$s märguanded" "Seadete muutmine" - - + "Kuva märguandetäpid" "Lisa ikoon avaekraanile" "Uute rakenduste puhul" "Ikooni kuju muutmine" diff --git a/res/values-fa/strings.xml b/res/values-fa/strings.xml index c24ebec249..539c7599c7 100644 --- a/res/values-fa/strings.xml +++ b/res/values-fa/strings.xml @@ -39,8 +39,7 @@ "درحال بارگیری برنامه‌‌ها…" "هیچ برنامه‌ای در مطابقت با «%1$s» پیدا نشد" "جستجوی برنامه‌های بیشتر" - - + "برنامه" "اعلان‌ها" "برای انتخاب میان‌بر، لمس کنید و نگه‌دارید." "برای انتخاب میان‌بر، دو ضربه سریع بزنید و نگه‌دارید یا از کنش‌های سفارشی استفاده کنید." @@ -66,7 +65,10 @@ "این برنامه سیستمی است و حذف نصب نمی‌شود." "پوشه بی‌نام" "%1$s غیرفعال شد" - + + %1$s، %2$d اعلان دارد + %1$s، %2$d اعلان دارد + "‏صفحه %1$d از %2$d" "‏صفحه اصلی %1$d از %2$d" "صفحه اصلی جدید" @@ -82,17 +84,13 @@ "توسط سرپرست سیستم غیرفعال شده است" "امکان دادن به چرخش صفحه اصلی" "وقتی تلفن چرخانده می‌شود" - - - - - - + "نقطه‌های اعلان" + "روشن" + "خاموش" "دسترسی به اعلان نیاز است" "برای نمایش «نقطه‌های اعلان»، اعلان‌های برنامه را برای %1$s روشن کنید" "تغییر تنظیمات" - - + "نمایش نقطه‌های اعلان" "افزودن نماد به صفحه اصلی" "برای برنامه‌های جدید" "تغییر شکل نماد" diff --git a/res/values-fi/strings.xml b/res/values-fi/strings.xml index 5cc6a5a477..70853bd235 100644 --- a/res/values-fi/strings.xml +++ b/res/values-fi/strings.xml @@ -39,8 +39,7 @@ "Ladataan sovelluksia…" "%1$s ei palauttanut sovelluksia." "Hae lisää sovelluksia" - - + "Sovellus" "Ilmoitukset" "Valitse pikakuvake painamalla sitä pitkään." "Valitse pikakuvake tai käytä muokattuja toimintoja kaksoisnapauttamalla ja painamalla pitkään." @@ -66,7 +65,10 @@ "Tämä on järjestelmäsovellus, eikä sitä voi poistaa." "Nimetön kansio" "%1$s poistettiin käytöstä" - + + %1$s: %2$d ilmoitusta + %1$s: %2$d ilmoitus + "Sivu %1$d / %2$d" "Aloitusruutu %1$d/%2$d" "Uusi aloitusnäytön sivu" @@ -82,17 +84,13 @@ "Järjestelmänvalvoja on poistanut toiminnon käytöstä." "Salli aloitusnäytön kiertäminen" "Kun puhelinta kierretään" - - - - - - + "Pistemerkit" + "Käytössä" + "Ei käytössä" "Ilmoituksien käyttöoikeus tarvitaan" "%1$s tarvitsee ilmoitusten käyttöoikeuden, jotta pistemerkkejä voidaan näyttää." "Muuta asetuksia" - - + "Näytä ilmoituksista kertovat pistemerkit" "Lisää kuvake aloitusruutuun" "Uusille sovelluksille" "Muuta kuvakkeen muotoa" diff --git a/res/values-fr-rCA/strings.xml b/res/values-fr-rCA/strings.xml index f82ea47462..2a70d3d153 100644 --- a/res/values-fr-rCA/strings.xml +++ b/res/values-fr-rCA/strings.xml @@ -39,8 +39,7 @@ "Chargement des applications en cours…" "Aucune application trouvée correspondant à « %1$s »" "Rechercher plus d\'applications" - - + "Application" "Notifications" "Maintenez un doigt sur le raccourci pour l\'ajouter" "Touchez 2x un raccourci et maintenez doigt dessus pour l’aj. ou utiliser des actions personnalisées." @@ -66,7 +65,10 @@ "Impossible de désinstaller cette application, car il s\'agit d\'une application système." "Dossier sans nom" "L\'application %1$s est désactivée" - + + %1$s a %2$d notification + %1$s a %2$d notificatio​ns + "Page %1$d sur %2$d" "Écran d\'accueil %1$d sur %2$d" "Nouvelle page d\'écran d\'accueil" @@ -82,17 +84,13 @@ "Cette fonction est désactivée par votre administrateur" "Autoriser la rotation de l\'écran d\'accueil" "Lorsque vous faites pivoter le téléphone" - - - - - - + "Points de notification" + "Activé" + "Désactivé" "L\'accès aux notifications est requis" "Pour afficher les points de notification, activez les notifications d\'application pour %1$s" "Modifier les paramètres" - - + "Afficher les points de notification" "Ajouter l\'icône à l\'écran d\'accueil" "Pour les nouvelles applications" "Modifier la forme de l\'icône" diff --git a/res/values-fr/strings.xml b/res/values-fr/strings.xml index 1d2e743d9d..35b8ca655f 100644 --- a/res/values-fr/strings.xml +++ b/res/values-fr/strings.xml @@ -39,8 +39,7 @@ "Chargement des applications…" "Aucune application ne correspond à la requête \"%1$s\"" "Rechercher plus d\'applications" - - + "Application" "Notifications" "Appui prolongé pour sélectionner un raccourci." "Appuyez 2X et maintenez la pression pour choisir un raccourci ou utilisez les actions personnalisées" @@ -66,7 +65,10 @@ "Impossible de désinstaller cette application, car il s\'agit d\'une application système." "Dossier sans nom" "%1$s est désactivé." - + + %1$s comporte %2$d notification + %1$s comporte %2$d notifications + "Page %1$d sur %2$d" "Écran d\'accueil %1$d sur %2$d" "Nouvelle page d\'écran d\'accueil" @@ -82,17 +84,13 @@ "Désactivé par votre administrateur" "Autoriser la rotation de l\'écran d\'accueil" "Lorsque vous faites pivoter le téléphone" - - - - - - + "Pastilles de notification" + "Activées" + "Désactivées" "Accès aux notifications requis" "Pour afficher les pastilles de notification, activez les notifications de l\'application %1$s" "Modifier les paramètres" - - + "Afficher les pastilles de notification" "Ajouter l\'icône à l\'écran d\'accueil" "Pour les nouvelles applications" "Modifier la forme des icônes" diff --git a/res/values-gl/strings.xml b/res/values-gl/strings.xml index 7e1788ad03..4a9f8dc6d7 100644 --- a/res/values-gl/strings.xml +++ b/res/values-gl/strings.xml @@ -39,8 +39,7 @@ "Cargando aplicacións…" "Non se atoparon aplicacións que coincidan con \"%1$s\"" "Buscar máis aplicacións" - - + "Aplicación" "Notificacións" "Mantén premido un atallo para seleccionalo." "Toca dúas veces e mantén premido para seleccionar un atallo ou utiliza accións personalizadas." @@ -66,7 +65,10 @@ "Esta aplicación é do sistema e non se pode desinstalar." "Cartafol sen nome" "Desactivouse %1$s" - + + A aplicación %1$s ten %2$d notificacións + A aplicación %1$s ten %2$d notificación + "Páxina %1$d de %2$d" "Pantalla de inicio %1$d de %2$d" "Nova páxina da pantalla de inicio" @@ -82,17 +84,13 @@ "Función desactivada polo administrador" "Permitir xirar a pantalla de inicio" "Ao xirar o teléfono" - - - - - - + "Puntos de notificacións" + "Opción activada" + "Opción desactivada" "Necesítase acceso ás notificacións" "Para que se mostren os puntos de notificacións, activa as notificacións da aplicación %1$s" "Cambiar configuración" - - + "Mostrar puntos de notificacións" "Engadir icona á pantalla de inicio" "Para novas aplicacións" "Cambiar forma das iconas" diff --git a/res/values-gu/strings.xml b/res/values-gu/strings.xml index 0db294f402..d0f7cc3f24 100644 --- a/res/values-gu/strings.xml +++ b/res/values-gu/strings.xml @@ -39,8 +39,7 @@ "ઍપ્લિકેશનો લોડ કરી રહ્યું છે…" "\"%1$s\"થી મેળ ખાતી કોઈ ઍપ્લિકેશનો મળી નથી" "વધુ ઍપ્લિકેશનો શોધો" - - + "ઍપ" "નોટિફિકેશનો" "એક શૉર્ટકટ ચૂંટવા માટે સ્પર્શ કરી રાખો." "એક શૉર્ટકટ ચૂંટવા અથવા કોઈ કસ્ટમ ક્રિયાઓનો ઉપયોગ કરવા માટે બે વાર ટૅપ કરીને દબાવી રાખો." @@ -66,7 +65,10 @@ "આ એક સિસ્ટમ ઍપ્લિકેશન છે અને અનઇન્સ્ટોલ કરી શકાતી નથી." "અનામી ફોલ્ડર" "%1$s અક્ષમ કરી" - + + %1$sના %2$d નોટિફિકેશન છે + %1$sના %2$d નોટિફિકેશન છે + "%2$d માંથી %1$d પૃષ્ઠ" "%2$d માંથી %1$d હોમ સ્ક્રીન" "નવું હોમ સ્ક્રીન પૃષ્ઠ" @@ -82,17 +84,13 @@ "તમારા વ્યવસ્થાપક દ્વારા અક્ષમ કરેલ" "હોમ સ્ક્રીનને ફેરવવાની મંજૂરી આપો" "જ્યારે ફોન ફેરવવામાં આવે ત્યારે" - - - - - - + "નોટિફિકેશન માટેના ચિહ્નો" + "ચાલુ છે" + "બંધ છે" "નોટિફિકેશનનો ઍક્સેસની જરૂરી છે" "નોટિફિકેશન માટેનું ચિહ્ન બતાવવા હેતુ, %1$s માટેની ઍપ્લિકેશન નોટિફિકેશન ચાલુ કરો" "સેટિંગ્સ બદલો" - - + "નોટિફિકેશન માટેના ચિહ્ન બતાવો" "હોમ સ્ક્રીન પર આઇકન ઉમેરો" "નવી ઍપ્લિકેશનો માટે" "આઇકનનો આકાર બદલો" diff --git a/res/values-hi/strings.xml b/res/values-hi/strings.xml index 992122625d..b37b17c8d2 100644 --- a/res/values-hi/strings.xml +++ b/res/values-hi/strings.xml @@ -39,8 +39,7 @@ "ऐप्लिकेशन लोड हो रहे हैं…" "\"%1$s\" से मिलता-जुलता कोई ऐप्लिकेशन नहीं मिला" "और ऐप सर्च करें" - - + "ऐप्लिकेशन" "सूचनाएं" "शॉर्टकट चुनने के लिए दबाकर रखें." "शॉर्टकट चुनने या पसंद के मुताबिक कार्रवाई करने के लिए दो बार टैप करें और कुछ देर दबाए रखें." @@ -52,7 +51,7 @@ "होम पेज" "निकालें" "अनइंस्टॉल करें" - "ऐप की जानकारी" + "ऐप्लिकेशन की जानकारी" "इंस्‍टॉल करें" "शॉर्टकट इंस्‍टॉल करें" "ऐप को उपयोगकर्ता के हस्‍तक्षेप के बिना शॉर्टकट जोड़ने देती है." @@ -66,7 +65,10 @@ "यह एक सिस्टम ऐप्लिकेशन है और इसे अनइंस्टॉल नहीं किया जा सकता." "अनामित फ़ोल्डर" "%1$s अक्षम है" - + + %1$s की %2$d सूचना है + %1$s की %2$d सूचनाएं हैं + "पेज %2$d में से %1$d" "होम स्क्रीन %2$d में से %1$d" "नया होम स्‍क्रीन पेज" @@ -82,17 +84,13 @@ "आपके एडमिन ने बंद किया हुआ है" "होमस्क्रीन घुमाने की अनुमति दें" "फ़ोन घुुमाए जाने पर" - - - - - - + "नई सूचनाएं बताने वाला गोल निशान" + "चालू" + "चालू" "सूचना के एक्सेस की ज़रूरत है" "सूचना बिंदु दिखाने के लिए, %1$s के ऐप्लिकेशन सूचना चालू करें" "सेटिंग बदलें" - - + "नई सूचनाएं बताने वाला गोल निशान दिखाएं" "होम स्क्रीन में आइकॉन जोड़ें" "नए ऐप्लिकेशन के लिए" "आइकॉन का आकार बदलें" diff --git a/res/values-hr/strings.xml b/res/values-hr/strings.xml index 26dc811be2..1d1919b30a 100644 --- a/res/values-hr/strings.xml +++ b/res/values-hr/strings.xml @@ -39,8 +39,7 @@ "Učitavanje aplikacija…" "Nema aplikacija podudarnih s upitom \"%1$s\"" "Traži više aplikacija" - - + "Aplikacija" "Obavijesti" "Dodirnite i zadržite kako biste podigli prečac." "Dvaput dodirnite i zadržite pritisak kako biste podigli prečac ili pokušajte prilagođenim radnjama." @@ -66,7 +65,11 @@ "Ovo je aplikacija sustava i ne može se ukloniti." "Neimenovana mapa" "Aplikacija %1$s onemogućena" - + + %1$s, ima %2$d obavijest + %1$s, ima %2$d obavijesti + %1$s, ima %2$d obavijesti + "Stranica %1$d od %2$d" "Početni zaslon %1$d od %2$d" "Nova stranica početnog zaslona" @@ -82,17 +85,13 @@ "Onemogućio administrator" "Dopusti zakretanje početnog zaslona" "Kada se telefon zakrene" - - - - - - + "Točke obavijesti" + "Uključeno" + "Isključeno" "Potreban je pristup obavijestima" "Za prikaz točaka obavijesti uključite obavijesti aplikacije %1$s" "Promjena postavki" - - + "Prikaži točke obavijesti" "Dodaj ikonu na početni zaslon" "Za nove aplikacije" "Promijeni oblik ikona" diff --git a/res/values-hu/strings.xml b/res/values-hu/strings.xml index b005f3ee8a..9f965bc4a2 100644 --- a/res/values-hu/strings.xml +++ b/res/values-hu/strings.xml @@ -39,8 +39,7 @@ "Alkalmazások betöltése…" "Nem található alkalmazás a(z) „%1$s” lekérdezésre" "További alkalmazások keresése" - - + "Alkalmazás" "Értesítések" "Felvételhez tartsa nyomva a parancsikont." "Parancsikon felvételéhez koppintson rá duplán és tartsa nyomva, vagy használjon egyéni műveleteket." @@ -66,7 +65,10 @@ "Ez egy rendszeralkalmazás, és nem lehet eltávolítani." "Névtelen mappa" "A(z) %1$s letiltva" - + + A(z) %1$s %2$d értesítéssel rendelkezik + A(z) %1$s %2$d értesítéssel rendelkezik + "%2$d/%1$d. oldal" "%2$d/%1$d. kezdőképernyő" "Új kezdőképernyő oldal" @@ -82,17 +84,13 @@ "A rendszergazda letiltotta" "A kezdőképernyő elforgatásának engedélyezése" "A telefon elforgatásakor" - - - - - - + "Értesítési pöttyök" + "Be" + "Ki" "Értesítésekhez való hozzáférésre van szükség" "Az értesítési pöttyök megjelenítéséhez kapcsolja be a(z) %1$s alkalmazás értesítéseit" "Beállítások módosítása" - - + "Értesítési pöttyök megjelenítése" "Ikon hozzáadása a kezdőképernyőhöz" "Új alkalmazásoknál" "Ikon formájának módosítása" diff --git a/res/values-hy/strings.xml b/res/values-hy/strings.xml index 6239ecdbd0..ca56102723 100644 --- a/res/values-hy/strings.xml +++ b/res/values-hy/strings.xml @@ -39,8 +39,7 @@ "Հավելվածների բեռնում…" %1$s» հարցմանը համապատասխանող հավելվածներ չեն գտնվել" "Որոնել այլ հավելվածներ" - - + "Հավելված" "Ծանուցումներ" "Կրկնակի հպեք և պահեք՝ դյուրանցում ընտրելու համար։" "Կրկնակի հպեք և պահեք՝ դյուրանցում ընտրելու համար կամ օգտվեք հարմարեցրած գործողություններից:" @@ -66,7 +65,10 @@ "Սա համակարգային ծրագիր է և չի կարող ապատեղադրվել:" "Անանուն պանակ" "%1$s հավելվածն անջատված է" - + + %1$s, ունի %2$d ծանուցում + %1$s, ունի %2$d ծանուցում + "Էջ %1$d՝ %2$d-ից" "Հիմնական էկրան %1$d` %2$d-ից" "Հիմնական էկրանի նոր էջ" @@ -82,17 +84,13 @@ "Անջատվել է ձեր ադմինիստրատորի կողմից" "Թույլ տալ հիմնական էկրանի պտտումը" "Հեռախոսը պտտելու դեպքում" - - - - - - + "Ծանուցումների կետիկներ" + "Միացված է" + "Անջատած է" "Անհրաժեշտ է ծանուցման թույլտվություն" "Ծանուցումների կետիկները ցուցադրելու համար միացրեք ծանուցումները %1$s-ի համար" "Փոխել կարգավորումները" - - + "Ցուցադրել ծանուցումների կետիկները" "Ավելացնել պատկերակը Հիմնական էկրանին" "Նոր հավելվածների համար" "Փոխել պատկերակների տեսքը" diff --git a/res/values-in/strings.xml b/res/values-in/strings.xml index 9f58265e6d..396c5b164b 100644 --- a/res/values-in/strings.xml +++ b/res/values-in/strings.xml @@ -39,8 +39,7 @@ "Memuat aplikasi…" "Tidak ditemukan aplikasi yang cocok dengan \"%1$s\"" "Telusuri aplikasi lainnya" - - + "Aplikasi" "Notifikasi" "Tap lama untuk memilih pintasan." "Tap dua kali & tahan untuk memilih pintasan atau menggunakan tindakan khusus." @@ -66,7 +65,10 @@ "Ini adalah aplikasi sistem dan tidak dapat dicopot pemasangannya." "Folder Tanpa Nama" "%1$s dinonaktifkan" - + + %1$s, memiliki %2$d notifikasi + %1$s, memiliki %2$d notifikasi + "Halaman %1$d dari %2$d" "Layar utama %1$d dari %2$d" "Halaman layar utama baru" @@ -82,17 +84,13 @@ "Dinonaktifkan oleh admin" "Izinkan layar Utama diputar" "Saat ponsel diputar" - - - - - - + "Titik notifikasi" + "Aktif" + "Nonaktif" "Perlu akses notifikasi" "Guna menampilkan Titik Notifikasi, aktifkan notifikasi aplikasi untuk %1$s" "Ubah setelan" - - + "Tampilkan titik notifikasi" "Tambahkan ikon ke Layar utama" "Untuk aplikasi baru" "Ubah bentuk ikon" diff --git a/res/values-is/strings.xml b/res/values-is/strings.xml index 2826861ffd..99bd888ade 100644 --- a/res/values-is/strings.xml +++ b/res/values-is/strings.xml @@ -39,8 +39,7 @@ "Hleður forrit…" "Ekki fundust forrit sem samsvara „%1$s“" "Leita að fleiri forritum" - - + "Forrit" "Tilkynningar" "Haltu fingri á flýtileið til að grípa hana." "Ýttu tvisvar og haltu fingri á flýtileið til að grípa hana eða notaðu sérsniðnar aðgerðir." @@ -66,7 +65,10 @@ "Þetta er kerfisforrit sem ekki er hægt að fjarlægja." "Ónefnd mappa" "Óvirkt %1$s" - + + %1$s, er með %2$d tilkynningu + %1$s, er með %2$d tilkynningar + "Síða %1$d af %2$d" "Heimaskjár %1$d af %2$d" "Ný síða á heimaskjá" @@ -82,17 +84,13 @@ "Gert óvirkt af kerfisstjóra" "Leyfa snúning fyrir heimaskjá" "Þegar símanum er snúið" - - - - - - + "Tilkynningapunktar" + "Kveikt" + "Slökkt" "Aðgangs að tilkynningum er krafist" "Til að sýna tilkynningarpunkta skaltu kveikja á forritstilkynningum fyrir %1$s" "Breyta stillingum" - - + "Sýna tilkynningapunkta" "Bæta tákni á heimaskjáinn" "Fyrir ný forrit" "Breyta formi tákns" diff --git a/res/values-it/strings.xml b/res/values-it/strings.xml index 825f914fe5..0bdc5afc97 100644 --- a/res/values-it/strings.xml +++ b/res/values-it/strings.xml @@ -39,8 +39,7 @@ "Caricamento delle app…" "Nessuna app trovata corrispondente a \"%1$s\"" "Cerca altre app" - - + "App" "Notifiche" "Tocca e tieni premuto per scegliere la scorciatoia" "Tocca due volte e tieni premuto per scegliere una scorciatoia o per usare azioni personalizzate." @@ -66,7 +65,10 @@ "Questa è un\'app di sistema e non può essere disinstallata." "Cartella senza nome" "App %1$s disattivata" - + + %1$s, ha %2$d notifiche + %1$s, ha %2$d notifica + "Pagina %1$d di %2$d" "Schermata Home %1$d di %2$d" "Nuova pagina Schermata Home" @@ -82,17 +84,13 @@ "Disattivata dall\'amministratore" "Consenti rotazione della schermata Home" "Con il telefono ruotato" - - - - - - + "Indicatori di notifica" + "On" + "Off" "Accesso alle notifiche necessario" "Per mostrare gli indicatori di notifica, attiva le notifiche per l\'app %1$s" "Modifica impostazioni" - - + "Mostra indicatori di notifica" "Aggiungi icone alla schermata Home" "Per le nuove app" "Cambia la forma delle icone" diff --git a/res/values-iw/strings.xml b/res/values-iw/strings.xml index 6104bc0d19..9525468f68 100644 --- a/res/values-iw/strings.xml +++ b/res/values-iw/strings.xml @@ -39,8 +39,7 @@ "טוען אפליקציות…" "לא נמצאו אפליקציות התואמות ל-\"%1$s\"" "חפש אפליקציות נוספות" - - + "אפליקציה" "התראות" "כדי להוסיף קיצור דרך, יש לגעת בו ולהחזיק אותו." "כדי להוסיף קיצור דרך או להשתמש בפעולות מותאמות אישית, יש להקיש על קיצור הדרך פעמיים ולהחזיק אותו." @@ -66,7 +65,12 @@ "זוהי אפליקציית מערכת ולא ניתן להסיר את התקנתה." "תיקיה ללא שם" "%1$s מושבתת" - + + לאפליקציה %1$s יש %2$d התראות + לאפליקציה %1$s יש %2$d התראות + לאפליקציה %1$s יש %2$d התראות + לאפליקציה %1$s יש התראה אחת (%2$d) + "‏דף %1$d מתוך %2$d" "‏מסך דף הבית %1$d מתוך %2$d" "מסך דף הבית חדש" @@ -82,17 +86,13 @@ "הושבת על ידי מנהל המערכת שלך" "אפשרות סיבוב של מסך דף הבית" "כאשר הטלפון מסובב" - - - - - - + "סימני ההתראות" + "מופעל" + "כבוי" "נדרשת גישה להתראות" "כדי להציג את סימני ההתראות,יש להפעיל התראות מהאפליקציה %1$s" "שנה את ההגדרות" - - + "הצגת סימני ההתראות" "הוספת סמל במסך דף הבית" "לאפליקציות חדשות" "שינוי הצורה של הסמלים" diff --git a/res/values-ja/strings.xml b/res/values-ja/strings.xml index 089b938d51..2c5da02c38 100644 --- a/res/values-ja/strings.xml +++ b/res/values-ja/strings.xml @@ -39,8 +39,7 @@ "アプリを読み込んでいます…" "「%1$s」に一致するアプリは見つかりませんでした" "他のアプリを検索" - - + "アプリ" "通知" "ショートカットを追加するには押し続けます。" "ダブルタップ後に押し続けてショートカットを選択するか、カスタム操作を使用してください。" @@ -66,7 +65,10 @@ "このシステムアプリはアンインストールできません。" "名前のないフォルダ" "「%1$s」は無効です" - + + %1$s の通知が %2$d 件あります + %1$s の通知が %2$d 件あります + "%1$d/%2$dページ" "ホーム画面: %1$d/%2$d" "新しいホーム画面ページ" @@ -82,17 +84,13 @@ "管理者により無効にされています" "ホーム画面の回転を許可" "スマートフォンが回転したとき" - - - - - - + "通知ドット" + "ON" + "OFF" "通知へのアクセス権限が必要" "通知ドットを表示するには、「%1$s」のアプリ通知を ON にしてください" "設定を変更" - - + "通知ドットの表示" "ホーム画面にアイコンを追加" "新しいアプリをダウンロードしたとき" "アイコンの形の変更" diff --git a/res/values-ka/strings.xml b/res/values-ka/strings.xml index fef1a48689..af603c86e0 100644 --- a/res/values-ka/strings.xml +++ b/res/values-ka/strings.xml @@ -39,8 +39,7 @@ "აპები იტვირთება…" "„%1$s“-ის თანხვედრი აპები არ მოიძებნა" "მეტი აპის პოვნა" - - + "აპი" "შეტყობინებები" "შეეხეთ და დააყოვნეთ მალსახმობის ასარჩევად." "ორმაგად შეეხეთ და გეჭიროთ მალსახმობის ასარჩევად ან მორგებული მოქმედებების გამოსაყენებლად." @@ -66,7 +65,10 @@ "ეს სისტემური აპია და მისი წაშლა შეუძლებელია." "უსახელო საქაღალდე" "%1$s გაითიშა" - + + %1$s-ში არის %2$d შეტყობინება + %1$s-ში არის %2$d შეტყობინება + "გვერდი %1$d %2$d-დან" "მთავარი ეკრანი %1$d, %2$d-დან" "მთავარი ეკრანის ახალი გვერდი" @@ -82,17 +84,13 @@ "გათიშულია თქვენი ადმინისტრატორის მიერ" "მთავარი ეკრანის შეტრიალების დაშვება" "ტელეფონის შეტრიალებისას" - - - - - - + "შეტყობინების ნიშნულები" + "ჩართულია" + "გამორთულია" "საჭიროა შეტყობინებებზე წვდომა" "შეტყობინებათა ნიშნულების საჩვენებლად, ჩართეთ აპის შეტყობინებები %1$s-ისთვის" "პარამეტრების შეცვლა" - - + "შეტყობინების ნიშნულების ჩვენება" "ხატულას მთავარ ეკრანზე დამატება" "ახალი აპებისთვის" "ხატულას ფორმის შეცვლა" diff --git a/res/values-kk/strings.xml b/res/values-kk/strings.xml index dd26eab103..135360e722 100644 --- a/res/values-kk/strings.xml +++ b/res/values-kk/strings.xml @@ -39,8 +39,7 @@ "Қолданбалар жүктелуде…" "\"%1$s\" сұрауына сәйкес келетін қолданбалар жоқ" "Қосымша қолданбалар іздеу" - - + "Қолданба" "Хабарландырулар" "Таңбашаны таңдау үшін оны басып, ұстап тұрыңыз." "Екі рет басып, ұстап тұрып, таңбашаны таңдаңыз немесе арнаулы әрекеттерді пайдаланыңыз." @@ -66,7 +65,10 @@ "Бұл жүйе қолданбасы, сондықтан оны алу мүмкін емес." "Атауы жоқ қалта" "%1$s өшірілді" - + + %1$s қолданбасында %2$d хабарландыру бар + %1$s қолданбасында %2$d хабарландыру бар + "%1$d бет, барлығы %2$d" "%1$d негізгі экран, барлығы %2$d" "Жаңа негізгі экран беті" @@ -82,17 +84,13 @@ "Әкімші өшірді" "Негізгі экранның бұрылуына рұқсат ету" "Телефон бұрылғанда" - - - - - - + "Хабарландыру белгілері" + "Қосулы" + "Өшірулі" "Хабарландыруға кіру рұқсаты қажет" "Хабарландыру белгілерін көрсету үшін %1$s қолданбасының қолданба хабарландыруларын қосыңыз" "Параметрлерді өзгерту" - - + "Хабарландыру белгілерін көрсету" "Негізгі экранға белгіше енгізу" "Жаңа қолданбаларға арналған" "Белгіше пішінін өзгерту" diff --git a/res/values-km/strings.xml b/res/values-km/strings.xml index c6e0ba4831..d7c8c8895b 100644 --- a/res/values-km/strings.xml +++ b/res/values-km/strings.xml @@ -39,8 +39,7 @@ "កំពុងផ្ទុកកម្មវិធី…" "រកមិនឃើញកម្មវិធី​ដែលត្រូវគ្នាជាមួយ \"%1$s\" ទេ" "ស្វែងរកកម្មវិធីច្រើនទៀត" - - + "កម្មវិធី" "ការ​ជូនដំណឹង" "ចុច​ឱ្យ​ជាប់​ដើម្បី​ជ្រើស​រើស​ផ្លូវកាត់​មួយ។" "ចុច​ពីរ​ដង ហើយ​ចុច​ឱ្យ​ជាប់​ដើម្បី​ជ្រើសរើស​ផ្លូវកាត់​មួយ ឬ​ប្រើ​សកម្មភាព​ផ្ទាល់ខ្លួន។" @@ -66,7 +65,10 @@ "នេះ​​​ជា​កម្មវិធី​ប្រព័ន្ធ មិន​អាច​លុប​បាន​ទេ។" "ថត​គ្មាន​ឈ្មោះ" "បានបិទដំណើរការ %1$s" - + + %1$s, មាន​ការជូនដំណឹង %2$d + %1$s, មាន​ការជូនដំណឺង %2$d + "ទំព័រ %1$d នៃ %2$d" "អេក្រង់​ដើម %1$d នៃ %2$d" "ទំព័រអេក្រង់ដើមថ្មី" @@ -82,17 +84,13 @@ "បានបិទដំណើរការដោយអ្នកគ្រប់គ្រងរបស់អ្នក" "អនុញ្ញាតការបងិ្វលអេក្រង់ដើម" "នៅពេលដែលបង្វិលទូរស័ព្ទរបស់អ្នក" - - - - - - + "ស្លាកជូនដំណឹង" + "បើក" + "បិទ" "តម្រូវ​ឲ្យមាន​សិទ្ធិចូល​ប្រើប្រាស់​ការជូនដំណឹង" "ដើម្បីបង្ហាញស្លាកជូនដំណឹង សូមបើកការជូនដំណឹងកម្មវិធីសម្រាប់ %1$s" "ប្ដូរ​ការកំណត់" - - + "បង្ហាញ​ស្លាក​ជូនដំណឹង" "បញ្ចូល​រូបតំណាង​ទៅ​អេក្រង់​ដើម" "សម្រាប់កម្មវិធីថ្មី" "ប្តូររូបរាងរូបតំណាង" diff --git a/res/values-kn/strings.xml b/res/values-kn/strings.xml index 965700e6a1..de9ee52cd3 100644 --- a/res/values-kn/strings.xml +++ b/res/values-kn/strings.xml @@ -39,8 +39,7 @@ "ಅಪ್ಲಿಕೇಶನ್‌ಗಳನ್ನು ಲೋಡ್ ಮಾಡಲಾಗುತ್ತಿದೆ..." "\"%1$s\" ಹೊಂದಿಕೆಯ ಯಾವುದೇ ಅಪ್ಲಿಕೇಶನ್‌ಗಳು ಕಂಡುಬಂದಿಲ್ಲ" "ಮತ್ತಷ್ಟು ಅಪ್ಲಿಕೇಶನ್‌ಗಳನ್ನು ಹುಡುಕಿ" - - + "ಆ್ಯಪ್" "ಅಧಿಸೂಚನೆಗಳು" "ಸ್ಪರ್ಶಿಸಿ ಮತ್ತು ಶಾರ್ಟ್‌ಕಟ್ ಆರಿಸಲು ಹೋಲ್ಡ್ ಮಾಡಿ." "ಡಬಲ್ ಟ್ಯಾಪ್ ಮಾಡಿ ಮತ್ತು ಶಾರ್ಟ್‌ಕಟ್ ಆರಿಸಿಕೊಳ್ಳಲು ಹೋಲ್ಡ್ ಮಾಡಿ ಅಥವಾ ಕಸ್ಟಮ್ ಕ್ರಿಯೆಗಳನ್ನು ಬಳಸಿ." @@ -66,7 +65,10 @@ "ಇದೊಂದು ಅಪ್ಲಿಕೇಶನ್ ಆಗಿದೆ ಮತ್ತು ಅಸ್ಥಾಪಿಸಲು ಸಾಧ್ಯವಿಲ್ಲ." "ಹೆಸರಿಲ್ಲದ ಫೋಲ್ಡರ್" "%1$s ನಿಷ್ಕ್ರಿಯಗೊಳಿಸಲಾಗಿದೆ" - + + %1$s, %2$d ಅಧಿಸೂಚನೆಗಳನ್ನು ಹೊಂದಿದೆ + %1$s, %2$d ಅಧಿಸೂಚನೆಗಳನ್ನು ಹೊಂದಿದೆ + "%2$d ರಲ್ಲಿ %1$d ಪುಟ" "%2$d ರಲ್ಲಿ %1$d ಮುಖಪುಟದ ಪರದೆ" "ಹೊಸ ಮುಖಪುಟ ಪರದೆ" @@ -82,17 +84,13 @@ "ನಿಮ್ಮ ನಿರ್ವಾಹಕರು ನಿಷ್ಕ್ರಿಯಗೊಳಿಸಿದ್ದಾರೆ" "ಮುಖಪುಟ ತಿರುಗುವಿಕೆಯನ್ನು ಅನುಮತಿಸಿ" "ಫೋನ್‌ ತಿರುಗಿಸಿದಾಗ" - - - - - - + "ಅಧಿಸೂಚನೆ ಡಾಟ್‌ಗಳು" + "ಆನ್ ಆಗಿದೆ" + "ಆಫ್ ಆಗಿದೆ" "ಅಧಿಸೂಚನೆ ಪ್ರವೇಶ ಅಗತ್ಯವಿದೆ" "ಅಧಿಸೂಚನೆ ಚುಕ್ಕೆಗಳನ್ನು ತೋರಿಸಲು, %1$s ಗೆ ಅಪ್ಲಿಕೇಶನ್‌ ಅಧಿಸೂಚನೆಗಳನ್ನು ಆನ್‌ ಮಾಡಿ" "ಸೆಟ್ಟಿಂಗ್‌‌ಗಳನ್ನು ಬದಲಾಯಿಸಿ" - - + "ಅಧಿಸೂಚನೆ ಡಾಟ್‌ಗಳನ್ನು ತೋರಿಸಿ" "ಮುಖಪುಟದ ಪರದೆಗೆ ಐಕಾನ್ ಸೇರಿಸಿ" "ಹೊಸ ಅಪ್ಲಿಕೇಶನ್‌ಗಳಿಗೆ" "ಐಕಾನ್ ಆಕಾರವನ್ನು ಬದಲಿಸಿ" diff --git a/res/values-ko/strings.xml b/res/values-ko/strings.xml index 3ed09e5ef9..121f141112 100644 --- a/res/values-ko/strings.xml +++ b/res/values-ko/strings.xml @@ -39,8 +39,7 @@ "앱 로드 중…" "\'%1$s\'과(와) 일치하는 앱이 없습니다." "더 많은 앱 검색" - - + "앱" "알림" "바로가기를 선택하려면 길게 터치하세요." "바로가기를 선택하려면 두 번 탭한 다음 길게 터치하거나 맞춤 동작을 사용하세요." @@ -66,7 +65,10 @@ "시스템 앱은 제거할 수 없습니다." "이름이 없는 폴더" "%1$s 사용 안함" - + + %1$s%2$d개의 알림이 있음 + %1$s%2$d개의 알림이 있음 + "페이지 %1$d/%2$d" "홈 화면 %1$d/%2$d" "새로운 홈 화면 페이지" @@ -82,17 +84,13 @@ "관리자가 사용 중지함" "홈 화면 회전 허용" "휴대전화 회전 시" - - - - - - + "알림 표시 점" + "사용" + "사용 안함" "알림 액세스 권한 필요" "알림 표시점을 표시하려면 %1$s의 앱 알림을 사용 설정하세요." "설정 변경" - - + "알림 표시 점 보기" "홈 화면에 아이콘 추가" "새로 설치한 앱에 적용" "아이콘 모양 변경" diff --git a/res/values-ky/strings.xml b/res/values-ky/strings.xml index 4db7c8d01c..301840c11f 100644 --- a/res/values-ky/strings.xml +++ b/res/values-ky/strings.xml @@ -39,8 +39,7 @@ "Колдонмолор жүктөлүүдө…" "\"%1$s\" сурамына дал келген колдонмолор табылган жок" "Көбүрөөк колдонмолорду издөө" - - + "Колдонмо" "Билдирмелер" "Кыска жолду тандоо үчүн басып туруңуз." "Кыска жолду тандоо үчүн эки жолу таптап, кармап туруңуз же ыңгайлаштырылган аракеттерди колдонуңуз." @@ -66,7 +65,10 @@ "Бул системдик колдонмо жана аны чечкенге болбойт." "Аты жок фолдер" "%1$s өчүрүлгөн" - + + %1$s, %2$d билдирмеси бар + %1$s, %2$d билдирмеси бар + "%2$d ичинен %1$d барак" "Үй экраны %2$d ичинен %1$d" "Жаңы башкы экран барагы" @@ -82,17 +84,13 @@ "Администраторуңуз өчүрүп койгон" "Башкы экранды айлантууга уруксат берүү" "Телефон айланганда" - - - - - - + "Билдирмелер белгилери" + "Күйүк" + "Өчүк" "Эскертмелерге уруксат берилиши керек" "Эскертме белгилерин көрсөтүү максатында, %1$s үчүн колдонмонун эскертмелерин күйгүзүү керек" "Жөндөөлөрдү өзгөртүү" - - + "Билдирмелер белгилерин көрсөтүү" "Башкы экранга сүрөтчө кошуу" "Жаңы колдонмолор үчүн" "Сүрөтчөнүн формасын өзгөртүү" diff --git a/res/values-land/dimens.xml b/res/values-land/dimens.xml index f0021956a1..b2f357565f 100644 --- a/res/values-land/dimens.xml +++ b/res/values-land/dimens.xml @@ -31,7 +31,5 @@ 5.5dp - - 0dp 16dp diff --git a/res/values-lo/strings.xml b/res/values-lo/strings.xml index 3c55a8712f..692e5afe97 100644 --- a/res/values-lo/strings.xml +++ b/res/values-lo/strings.xml @@ -39,8 +39,7 @@ "ກໍາລັງໂຫຼດແອັບ…" "ບໍ່ພົບແອັບທີ່ກົງກັບ \"%1$s\"" "ຊອກຫາແອັບເພີ່ມເຕີມ" - - + "ແອັບ" "ການແຈ້ງເຕືອນ" "ແຕະຄ້າງໄວ້ເພື່ອຮັບປຸ່ມລັດ." "ແຕະສອງເທື່ອຄ້າງໄວ້ເພື່ອຮັບປຸ່ມລັດ ຫຼື ໃຊ້ຄຳສັ່ງແບບກຳນົດເອງ." @@ -66,7 +65,10 @@ "ນີ້ແມ່ນແອັບຯຂອງລະບົບ ແລະບໍ່ສາມາດຖອນການຕິດຕັ້ງອອກໄດ້." "ໂຟນເດີຍັງບໍ່ຖືກຕັ້ງຊື່" "ປິດການນຳໃຊ້ %1$s ແລ້ວ" - + + %1$s, ມີ %2$d ການແຈ້ງເຕືອນ + %1$s, ມີ %2$d ການແຈ້ງເຕືອນ + "ໜ້າ %1$d ຈາກ %2$d" "ໜ້າຈໍຫຼັກ %1$d ໃນ %2$d" "ໜ້າ​ຂອງ​ໜ້າ​ຈໍ​ຫຼັກ​ໃໝ່" @@ -82,17 +84,13 @@ "ຖືກປິດການນຳໃຊ້ໂດຍຜູ້ເບິ່ງແຍງລະບົບຂອງທ່ານ" "ອະນຸຍາດໃຫ້ໝຸນໜ້າຈໍທຳອິດໄດ້" "ເມື່ອໝຸນໂທລະສັບ" - - - - - - + "ຈຸດການແຈ້ງເຕືອນ" + "ເປີດ" + "ປິດ" "ຕ້ອງໃຊ້ການເຂົ້າເຖິງການແຈ້ງເຕືອນ" "ເພື່ອສະແດງຈຸດການແຈ້ງເຕືອນ, ໃຫ້ເປີດການແຈ້ງເຕືອນສຳລັບ %1$s" "ບັນທຶກການຕັ້ງຄ່າ" - - + "ສະແດງຈຸດການແຈ້ງເຕືອນ" "ເພີ່ມໄອຄອນໃສ່ໜ້າຈໍຫຼັກ" "ສຳລັບແອັບໃໝ່" "ປ່ຽນຮູບຮ່າງໄອຄອນ" diff --git a/res/values-lt/strings.xml b/res/values-lt/strings.xml index 592eb9d821..22d0c7be73 100644 --- a/res/values-lt/strings.xml +++ b/res/values-lt/strings.xml @@ -39,8 +39,7 @@ "Įkeliamos programos…" "Nerasta jokių užklausą „%1$s“ atitinkančių programų" "Ieškoti daugiau programų" - - + "Programa" "Pranešimai" "Paliesk. ir palaikyk., kad pasirinkt. spart. klav." "Dukart palieskite ir palaikykite, kad pasirinkt. spartųjį klavišą ar naudotumėte tinkintus veiksmus." @@ -66,7 +65,12 @@ "Tai sistemos programa ir jos negalima pašalinti." "Aplankas be pavadinimo" "„%1$s“ išjungta" - + + Programoje „%1$s“ yra %2$d pranešimas + Programoje „%1$s“ yra %2$d pranešimai + Programoje „%1$s“ yra %2$d pranešimo + Programoje „%1$s“ yra %2$d pranešimų + "%1$d psl. iš %2$d" "%1$d pagrindinis ekranas iš %2$d" "Naujas pagrindinio ekrano puslapis" @@ -82,17 +86,13 @@ "Išjungė administratorius" "Leisti pasukti pagrindinį ekraną" "Kai telefonas pasukamas" - - - - - - + "Pranešimų taškai" + "Įjungta" + "Išjungta" "Reikalinga prieiga prie pranešimų" "Kad būtų rodomi pranešimų taškai, įjunkite programos „%1$s“ pranešimus." "Keisti nustatymus" - - + "Rodyti pranešimų taškus" "Pridėti piktogr. prie pagrindinio ekrano" "Skirta naujoms programoms" "Pakeisti piktogramos formą" diff --git a/res/values-lv/strings.xml b/res/values-lv/strings.xml index 174cda2a63..f9240bd4da 100644 --- a/res/values-lv/strings.xml +++ b/res/values-lv/strings.xml @@ -39,8 +39,7 @@ "Notiek lietotņu ielāde…" "Vaicājumam “%1$s” neatbilda neviena lietotne" "Meklēt citas lietotnes" - - + "Lietotne" "Paziņojumi" "Lai atlasītu saīsni, pieskarieties un turiet to." "Lai atlasītu saīsni, veiciet dubultskārienu uz tās un turiet to. Varat arī veikt pielāgotas darbības." @@ -66,7 +65,11 @@ "Šī ir sistēmas lietotne, un to nevar atinstalēt." "Mape bez nosaukuma" "Lietotne %1$s ir atspējota" - + + Lietotnē %1$s ir %2$d paziņojumi + Lietotnē %1$s ir %2$d paziņojums + Lietotnē %1$s ir %2$d paziņojumi + "%1$d. lapa no %2$d" "Sākuma ekrāns: %1$d no %2$d" "Jauna sākuma ekrāna lapa" @@ -82,17 +85,13 @@ "Atspējojis administrators" "Atļaut sākuma ekrāna pagriešanu" "Pagriežot tālruni" - - - - - - + "Paziņojumu punkti" + "Ieslēgts" + "Izslēgts" "Nepieciešama piekļuve paziņojumiem" "Lai tiktu rādīti paziņojumu punkti, ieslēdziet paziņojumus lietotnei %1$s." "Mainīt iestatījumus" - - + "Rādīt paziņojumu punktus" "Pievienot ikonu sākuma ekrānā" "Jaunām lietotnēm" "Mainīt ikonu formu" diff --git a/res/values-mk/strings.xml b/res/values-mk/strings.xml index 4252a78e76..d40b925ebf 100644 --- a/res/values-mk/strings.xml +++ b/res/values-mk/strings.xml @@ -39,8 +39,7 @@ "Се вчитуваат апликации…" "Не се најдени апликации што одговараат на „%1$s“" "Пребарај други апликации" - - + "Апликација" "Известувања" "Допрете двапати и задржете за избор на кратенка." "Допрете двапати и задржете за избор на кратенка или користете приспособени дејства." @@ -66,7 +65,10 @@ "Ова е системска апликација и не може да се деинсталира." "Неименувана папка" "%1$s е оневозможена" - + + %1$s има %2$d известување + %1$s има %2$d известувања + "Страница %1$d од %2$d" "Екран на почетна страница %1$d од %2$d" "Нова страница на почетен екран" @@ -82,17 +84,13 @@ "Оневозможено од администраторот" "Дозволете ротација на Почетниот екран" "Кога телефонот се ротира" - - - - - - + "Точки за известување" + "Вклучено" + "Исклучено" "Потребен е пристап до известувањата" "За да се прикажуваат „Точки за известување“, вклучете ги известувањата за апликацијата %1$s" "Промени ги поставките" - - + "Прикажувај точки за известување" "Додај икона на почетниот екран" "За нови апликации" "Променете ја формата на иконата" diff --git a/res/values-ml/strings.xml b/res/values-ml/strings.xml index f34d58c904..93426ad1e0 100644 --- a/res/values-ml/strings.xml +++ b/res/values-ml/strings.xml @@ -39,8 +39,7 @@ "ആപ്പുകൾ ലോഡുചെയ്യുന്നു..." "\"%1$s\" എന്നതുമായി പൊരുത്തപ്പെടുന്ന ആപ്പുകളൊന്നും കണ്ടെത്തിയില്ല" "കൂടുതൽ ആപ്പുകൾക്ക് തിരയുക" - - + "ആപ്പ്" "അറിയിപ്പുകൾ" "തിരഞ്ഞെടുക്കുന്നതിന് കുറുക്കുവഴി സ്‌പർശിച്ച് പിടിക്കുക." "കുറുക്കുവഴി തിരഞ്ഞെടുക്കാനോ ഇഷ്‌ടാനുസൃത പ്രവർത്തനങ്ങൾ ഉപയോഗിക്കാനോ 2 തവണ ടാപ്പ് ചെയ്‌ത് പിടിക്കുക." @@ -66,7 +65,10 @@ "ഇതൊരു സിസ്‌റ്റം അപ്ലിക്കേഷനായതിനാൽ അൺഇൻസ്‌റ്റാളുചെയ്യാനാവില്ല." "പേരുനൽകാത്ത ഫോൾഡർ" "%1$s പ്രവർത്തനരഹിതമാക്കി" - + + %1$s എന്ന ആപ്പിന്, %2$d അറിയിപ്പുകൾ ഉണ്ട് + %1$s എന്ന ആപ്പിന്, %2$d അറിയിപ്പുണ്ട് + "പേജ് %1$d / %2$d" "ഹോം സ്‌ക്രീൻ %1$d / %2$d" "പുതിയ ഹോം സ്ക്രീൻ പേജ്" @@ -82,17 +84,13 @@ "അഡ്മിൻ പ്രവർത്തനരഹിതമാക്കിയിരിക്കുന്നു" "ഹോം സ്ക്രീൻ തിരിക്കൽ അനുവദിക്കുക" "ഫോൺ തിരിച്ച നിലയിലായിരിക്കുമ്പോൾ" - - - - - - + "അറിയിപ്പ് ഡോട്ടുകൾ" + "ഓണാണ്" + "ഓഫാണ്" "അറിയിപ്പിനായുള്ള ആക്‌സസ് ആവശ്യമാണ്" "അറിയിപ്പ് ഡോട്ടുകൾ കാണിക്കുന്നതിന്, %1$s എന്നയാളിനായുള്ള ആപ്പ് അറിയിപ്പുകൾ ഓണാക്കുക" "ക്രമീകരണം മാറ്റുക" - - + "അറിയിപ്പ് ഡോട്ടുകൾ കാണിക്കുക" "ഹോം സ്ക്രീനിലേക്ക് ഐക്കൺ ചേർക്കുക" "പുതിയ ആപ്പുകൾക്ക്" "ഐക്കണിന്റെ ആകാരം മാറ്റുക" diff --git a/res/values-mn/strings.xml b/res/values-mn/strings.xml index e6c9af827f..1b8b3efd8a 100644 --- a/res/values-mn/strings.xml +++ b/res/values-mn/strings.xml @@ -39,8 +39,7 @@ "Аппыг ачааллаж байна..." "\"%1$s\"-д тохирох апп олдсонгүй" "Бусад апп-г хайх" - - + "Апп" "Мэдэгдэл" "Товчлол авах бол удаан дарна уу." "Товчлол авах эсвэл тохируулсан үйлдлийг ашиглахын тулд давхар товшоод хүлээнэ үү." @@ -66,7 +65,10 @@ "Энэ апп нь системийн апп ба устгах боломжгүй." "Нэргүй фолдер" "%1$s-г идэвхгүй болгосон" - + + %1$s, %2$d мэдэгдэлтэй байна + %1$s, %2$d мэдэгдэлтэй байна + "%2$d-н %1$d хуудас" "%2$d-н Нүүр дэлгэц %1$d" "Шинэ үндсэн нүүр хуудас" @@ -82,17 +84,13 @@ "Таны админ идэвхгүй болгосон" "Нүүр дэлгэцийг эргүүлэхийг зөвшөөрөх" "Утсыг эргүүлсэн үед" - - - - - - + "Мэдэгдлийн цэг" + "Асаалттай" + "Унтраалттай" "Мэдэгдлийн хандалт шаардлагатай" "Мэдэгдлийн цэгийг харуулахын тулд %1$s-д аппын мэдэгдлийг асаана уу" "Тохиргоог өөрчлөх" - - + "Мэдэгдлийн цэгийг харуулах" "Нүүр хуудаст дүрс тэмдэг нэмэх" "Шинэ аппад зориулсан" "Дүрс тэмдгийн хэлбэрийг өөрчлөх" diff --git a/res/values-mr/strings.xml b/res/values-mr/strings.xml index 022cb7b51c..4f0de654d6 100644 --- a/res/values-mr/strings.xml +++ b/res/values-mr/strings.xml @@ -22,8 +22,8 @@ "Launcher3" "कार्य" - "अॅप इंस्टॉल केलेला नाही." - "अॅप उपलब्ध नाही" + "अ‍ॅप इंस्टॉल केलेला नाही." + "अ‍ॅप उपलब्ध नाही" "डाउनलोड केलेला अ‍ॅप सुरक्षित मोड मध्‍ये अक्षम केला" "विजेट सुरक्षित मोडमध्ये अक्षम झाले" "शॉर्टकट उपलब्ध नाही" @@ -39,8 +39,7 @@ "अॅप्स लोड करत आहे…" "\"%1$s\" शी जुळणारे कोणतेही अॅप्स आढळले नाहीत" "अधिक अॅप्स शोधा" - - + "अॅप" "सूचना" "शॉर्टकट निवडण्यासाठी स्पर्श करा आणि धरून ठेवा." "शॉर्टकट निवडण्यासाठी किंवा कस्टम क्रिया वापरण्यासाठी दोनदा टॅप करा आणि धरून ठेवा." @@ -52,21 +51,24 @@ "होम" "काढा" "अनइंस्टॉल करा" - "अॅप माहिती" + "अ‍ॅप माहिती" "इंस्टॉल करा" "शॉर्टकट स्‍थापित करा" - "वापरकर्ता हस्तक्षेपाशिवाय शॉर्टकट जोडण्यास अॅप ला अनुमती देते." + "वापरकर्ता हस्तक्षेपाशिवाय शॉर्टकट जोडण्यास अ‍ॅप ला अनुमती देते." "होम सेटिंग्ज आणि शॉर्टकट वाचा" - "मुख्यपृष्ठातील सेटिंग्ज आणि शॉर्टकट वाचण्यास अॅप ला अनुमती देते." + "मुख्यपृष्ठातील सेटिंग्ज आणि शॉर्टकट वाचण्यास अ‍ॅप ला अनुमती देते." "होम सेटिंग्ज आणि शॉर्टकट लिहा" - "मुख्यपृष्ठातील सेटिंग्ज आणि शॉर्टकट बदलण्यास अॅप ला अनुमती देते." + "मुख्यपृष्ठातील सेटिंग्ज आणि शॉर्टकट बदलण्यास अ‍ॅप ला अनुमती देते." "%1$s ला फोन कॉल करण्याची अनुमती नाही" "विजेट लोड करण्यात समस्या" "सेटअप" - "हा सिस्टम अॅप आहे आणि अनइंस्टॉल केला जाऊ शकत नाही." + "हा सिस्टम अ‍ॅप आहे आणि अनइंस्टॉल केला जाऊ शकत नाही." "अनामित फोल्डर" "%1$s अक्षम केला आहे" - + + %1$sसाठी%2$dसूचना आहेत + %1$sसाठी %2$d सूचना आहेत + "%2$d पैकी %1$d पृष्ठ" "%2$d पैकी %1$d मुख्य स्क्रीन" "नवीन मुख्य स्क्रीन पृष्ठ" @@ -82,17 +84,13 @@ "आपल्या प्रशासकाने अक्षम केले" "मुख्यस्क्रीन फिरविण्‍यास अनुमती द्या" "फोन फिरविला जातो तेव्हा" - - - - - - + "सूचना बिंदू" + "सुरू" + "बंद" "सूचनांच्या अ‍ॅक्सेसची आवश्यकता आहे" "सूचना बिंदू दाखवण्यासाठी, %1$s साठी अ‍ॅप सूचना चालू करा" "सेटिंग्ज बदला" - - + "सूचना बिंदू दाखवा" "होम स्क्रीनवर आयकन जोडा" "नवीन अॅप्ससाठी" "चिन्हाचा आकार बदला" @@ -106,8 +104,8 @@ "अज्ञात" "काढा" "शोधा" - "हा अॅप इंस्टॉल केलेला नाही" - "या चिन्हासाठी अॅप इंस्टॉल केलेला नाही. तुम्ही ते काढू शकता किंवा अॅपचा शोध घेऊ शकता आणि त्यास व्यक्तिचलितपणे इंस्टॉल करू शकता." + "हा अ‍ॅप इंस्टॉल केलेला नाही" + "या चिन्हासाठी अ‍ॅप इंस्टॉल केलेला नाही. तुम्ही ते काढू शकता किंवा अ‍ॅपचा शोध घेऊ शकता आणि त्यास व्यक्तिचलितपणे इंस्टॉल करू शकता." "%1$s डाउनलोड होत आहे , %2$s पूर्ण झाले" "%1$s इंस्टॉल करण्याची प्रतिक्षा करत आहे" "%1$s विजेट" diff --git a/res/values-ms/strings.xml b/res/values-ms/strings.xml index e30d632687..4654209c1a 100644 --- a/res/values-ms/strings.xml +++ b/res/values-ms/strings.xml @@ -39,8 +39,7 @@ "Memuatkan apl…" "Tiada apl yang ditemui sepadan dengan \"%1$s\"" "Cari lagi apl" - - + "Apl" "Pemberitahuan" "Sentuh & tahan untuk mengambil pintasan." "Ketik dua kali & tahan untuk mengambil pintasan atau menggunakan tindakan tersuai." @@ -66,7 +65,10 @@ "Ini ialah apl sistem dan tidak boleh dinyahpasang." "Folder Tanpa Nama" "%1$s dilumpuhkan" - + + %1$s, mempunyai %2$d pemberitahuan + %1$s, mempunyai %2$d pemberitahuan + "Halaman %1$d daripada %2$d" "Skrin Laman Utama %1$d daripada %2$d" "Halaman skrin utama baharu" @@ -82,17 +84,13 @@ "Dilumpuhkan oleh pentadbir anda" "Benarkan putaran Skrin Utama" "Apabila telefon diputar" - - - - - - + "Titik pemberitahuan" + "Hidup" + "Mati" "Akses pemberitahuan diperlukan" "Untuk menunjukkan Titik Pemberitahuan, hidupkan pemberitahuan apl untuk %1$s" "Tukar tetapan" - - + "Tunjukkan titik pemberitahuan" "Tambahkan ikon pada Skrin Utama" "Untuk apl baharu" "Tukar bentuk ikon" diff --git a/res/values-my/strings.xml b/res/values-my/strings.xml index 06598e06fb..f25de749f2 100644 --- a/res/values-my/strings.xml +++ b/res/values-my/strings.xml @@ -39,8 +39,7 @@ "အက်ပ်များကို ဖွင့်နေသည်…" "\"%1$s\" နှင့်ကိုက်ညီသည့် အပ်ပ်များကို မတွေ့ပါ" "နောက်ထပ် အက်ပ်များကို ရှာပါ" - - + "အက်ပ်" "အကြောင်းကြားချက်များ" "ဖြတ်လမ်းလင့်ခ်တစ်ခုကို ရွေးရန် ထိပြီး ဖိထားပါ။" "ဖြတ်လမ်းလင့်ခ်ကို ရွေးရန် (သို့) စိတ်ကြိုက်လုပ်ဆောင်ချက်များကို သုံးရန် နှစ်ချက်တို့ပြီး ဖိထားပါ။" @@ -66,7 +65,10 @@ "ဤအပ်ပလီကေးရှင်းမှာ စစ်စတန်ပိုင်းဆိုင်ရာ အပ်ပလီကေးရှင်းဖြစ်ပါသည်။ ထုတ်ပစ်၍ မရပါ" "အမည်မရှိအကန့်" "%1$s ကို ပိတ်ထားသည်" - + + %1$s တွင် သတိပေးချက် %2$d ခု ရှိသည် + %1$s တွင် သတိပေးချက် %2$d ခု ရှိသည် + "စာမျက်နှာ %1$d မှ %2$d" "ပင်မစာမျက်နှာ %1$d မှ %2$d" "ပင်မမျက်နှာပြင် စာမျက်နှာသစ်" @@ -82,17 +84,13 @@ "သင့်စီမံခန့်ခွဲသူက ပိတ်လိုက်ပါသည်" "ပင်မစာမျက်နှာလှည့်ခြင်းကို ခွင့်ပြုပါ" "ဖုန်းကိုလှည့်ထားစဉ်" - - - - - - + "သတိပေးချက် အစက်များ" + "ဖွင့်ထားသည်" + "ပိတ်ထားသည်" "အကြောင်းကြားချက် အသုံးပြုခွင့် လိုအပ်သည်" "အကြောင်းကြားချက် အစက်များကို ပြသရန် %1$s အတွက် အက်ပ်အကြောင်းကြားချက်များကို ဖွင့်ပါ" "ဆက်တင်များ ပြောင်းရန်" - - + "သတိပေးချက် အစက်များ ပြရန်" "ပင်မစာမျက်နှာသို့ သင်္ကေတပုံ ထည့်ရန်" "အက်ပ်အသစ်များအတွက်" "သင်္ကေတပုံစံကို ပြောင်းရန်" diff --git a/res/values-nb/strings.xml b/res/values-nb/strings.xml index 6534f85fbc..7400beb62d 100644 --- a/res/values-nb/strings.xml +++ b/res/values-nb/strings.xml @@ -39,8 +39,7 @@ "Laster inn appene …" "Fant ingen apper som samsvarer med «%1$s»" "Søk etter flere apper" - - + "App" "Varsler" "Trykk og hold for å velge en snarvei." "Dobbelttrykk og hold for å velge en snarvei eller bruke tilpassede handlinger." @@ -66,7 +65,10 @@ "Dette er en systemapp som ikke kan avinstalleres." "Mappe uten navn" "Slo av %1$s" - + + %1$s har %2$d varsler + %1$s har %2$d varsel + "Side %1$d av %2$d" "Startside %1$d av %2$d" "Ny side på startskjermen" @@ -82,17 +84,13 @@ "Administratoren har slått av funksjonen" "Tillat rotasjon av startskjermen" "Når telefonen roteres" - - - - - - + "Varselsprikker" + "På" + "Av" "Tilgang til varsler er nødvendig" "Slå på appvarsler for %1$s for å vise varselsprikker" "Endre innstillingene" - - + "Vis varselsprikker" "Legg til ikon på startsiden" "For nye apper" "Endre formen på ikonet" diff --git a/res/values-ne/strings.xml b/res/values-ne/strings.xml index d205109d7b..be40ec162d 100644 --- a/res/values-ne/strings.xml +++ b/res/values-ne/strings.xml @@ -39,8 +39,7 @@ "अनुप्रयोगहरू लोड गर्दै…" "\"%1$s\" सँग मिल्दो कुनै अनुप्रयोग भेटिएन" "थप अनुप्रयोगहरू खोज्नुहोस्" - - + "अनुप्रयोग" "सूचनाहरू" "कुनै सर्टकट छनौट गर्न छोइराख्नुहोस्।" "कुनै सर्टकट छनौट गर्न वा रोजेका कारबाहीहरू प्रयोग गर्न डबल ट्याप गरेर छोइराख्नुहोस्।" @@ -66,7 +65,10 @@ "यो प्रणाली अनुप्रयोग हो र यसलाई स्थापना रद्द गर्न सकिँदैन।" "बेनाम फोल्डर" "असक्षम पारिएको %1$s" - + + %1$s, का %2$d सूचनाहरू छन् + %1$s, को %2$d सूचना छ + "पृष्ठ %2$d को %1$d" "गृह स्क्रिन %1$d को %2$d" "नयाँ गृह स्क्रिन पृष्ठ" @@ -82,17 +84,13 @@ "तपाईँको प्रशासकद्वारा असक्षम गरिएको" "गृह स्क्रिनलाई घुम्ने अनुमति दिनुहोस्" "फोनलाई घुमाइँदा" - - - - - - + "सूचनाको प्रतीक जनाउने थोप्लाहरू" + "सक्रिय" + "निष्क्रिय" "सूचनासम्बन्धी पहुँच आवश्यक हुन्छ" "सूचनाको प्रतीक जनाउने थोप्लाहरू देखाउन %1$s को अनुप्रयोगसम्बन्धी सूचनाहरूलाई सक्रिय गर्नुहोस्" "सेटिङहरू बदल्नुहोस्" - - + "सूचनाको प्रतीक जनाउने थोप्लाहरू देखाउनुहोस्" "गृह स्क्रिनमा आइकन थप्नुहोस्" "नयाँ अनुप्रयोगका लागि" "आइकनको आकार परिवर्तन गर्नुहोस्" diff --git a/res/values-nl/strings.xml b/res/values-nl/strings.xml index e4b22332a5..b0e0cb23ce 100644 --- a/res/values-nl/strings.xml +++ b/res/values-nl/strings.xml @@ -39,8 +39,7 @@ "Apps laden…" "Er zijn geen apps gevonden die overeenkomen met \'%1$s\'" "Zoeken naar meer apps" - - + "App" "Meldingen" "Tik en houd vast om snelkoppeling toe te voegen." "Dubbeltik en houd vast om een snelkoppeling toe te voegen of aangepaste acties te gebruiken." @@ -66,7 +65,10 @@ "Dit is een systeemapp die niet kan worden verwijderd." "Naamloze map" "%1$s is uitgeschakeld" - + + %1$s, heeft %2$d meldingen + %1$s, heeft %2$d melding + "Pagina %1$d van %2$d" "Startscherm %1$d van %2$d" "Nieuwe startschermpagina" @@ -82,17 +84,13 @@ "Uitgeschakeld door je beheerder" "Draaien van startscherm toestaan" "Wanneer de telefoon gedraaid is" - - - - - - + "Meldingsstipjes" + "Aan" + "Uit" "Toegang tot meldingen vereist" "Als je meldingsstipjes wilt weergeven, schakel je app-meldingen in voor %1$s" "Instellingen wijzigen" - - + "Meldingsstipjes weergeven" "Pictogram toevoegen aan startscherm" "Voor nieuwe apps" "Vorm van pictogram wijzigen" diff --git a/res/values-or/strings.xml b/res/values-or/strings.xml index e4351c18c7..8c2632529c 100644 --- a/res/values-or/strings.xml +++ b/res/values-or/strings.xml @@ -39,8 +39,7 @@ "ଆପ୍‌ ଲୋଡ୍‌ ହେଉଛି..." "\"%1$s\" ସହିତ ମେଳ ହେଉଥିବା କୌଣସି ଆପ୍‌ ମିଳିଲା ନାହିଁ" "ଅଧିକ ଆପ୍‌ ଖୋଜନ୍ତୁ" - - + "ଆପ୍" "ବିଜ୍ଞପ୍ତି" "ଏକ ଶର୍ଟକଟ୍ ଚୟନ କରିବାକୁ ସ୍ପର୍ଶ କରି ଧରି ରଖନ୍ତୁ।" "ଡବଲ୍‌-ଟାପ୍‌ କରନ୍ତୁ ଏବଂ ଏକ ଶର୍ଟକଟ୍ ଚୟନ କରିବାକୁ ଧରି ରଖନ୍ତୁ କିମ୍ୱା କଷ୍ଟମ୍ ପ୍ରକ୍ରିୟା ବ୍ୟବହାର କରନ୍ତୁ।" @@ -66,7 +65,10 @@ "ଏହା ଏକ ସିଷ୍ଟମ୍‌ ଆପ୍‌ ଅଟେ ଏବଂ ଏହା ଅନଇନଷ୍ଟଲ୍‌ କରାଯାଇ ପାରିବ ନାହିଁ।" "ବେନାମୀ ଫୋଲ୍ଡର୍‌" "%1$s ଅକ୍ଷମ କରାଗଲା" - + + %1$s, %2$dଟି ବିଜ୍ଞପ୍ତି ରହିଛି + %1$s, %2$dଟି ବିଜ୍ଞପ୍ତି ରହିଛି + "ମୋଟ %2$dରୁ %1$d ନମ୍ବର ପୃଷ୍ଠା" "%2$dରୁ %1$d ହୋମ୍‌ ସ୍କ୍ରୀନ୍" "ନୂଆ ହୋମ୍‌ ସ୍କ୍ରୀନ୍‌ ପୃଷ୍ଠା" @@ -82,17 +84,13 @@ "ଆପଣଙ୍କ ଆଡମିନଙ୍କ ଦ୍ୱାରା ଅକ୍ଷମ କରାଯାଇଛି" "ହୋମ୍‌ ସ୍କ୍ରୀନ୍ ବୁଲାଇବା ଅନୁମତି ଦିଅନ୍ତୁ" "ଯେତେବେଳେ ଫୋନକୁ ବୁଲାଯାଇଥାଏ" - - - - - - + "ବିଜ୍ଞପ୍ତି ବିନ୍ଦୁଗୁଡ଼ିକ" + "ଚାଲୁ କରନ୍ତୁ" + "ବନ୍ଦ କରନ୍ତୁ" "ବିଜ୍ଞପ୍ତି ଆକ୍ସେସ୍‌ ଆବଶ୍ୟକ ଅଟେ" "ବିଜ୍ଞପ୍ତି ବିନ୍ଦୁ ଦେଖାଇବାକୁ, %1$s ପାଇଁ ଆପ୍‌ ବିଜ୍ଞପ୍ତି ଅନ୍‌ କରନ୍ତୁ" "ସେଟିଙ୍ଗ ପରିବର୍ତ୍ତନ କରନ୍ତୁ" - - + "ବିଜ୍ଞପ୍ତି ଡଟ୍‌ଗୁଡ଼ିକୁ ଦେଖାନ୍ତୁ" "ହୋମ୍‌ ସ୍କ୍ରୀନରେ ଆଇକନ୍‌କୁ ଯୋଡ଼ନ୍ତୁ" "ନୂଆ ଆପ୍‌ ପାଇଁ" "ଆଇକନ୍‌ର ଆକାର ପରିବର୍ତ୍ତନ କରନ୍ତୁ" diff --git a/res/values-pa/strings.xml b/res/values-pa/strings.xml index ba75e1e205..f21fc0df16 100644 --- a/res/values-pa/strings.xml +++ b/res/values-pa/strings.xml @@ -39,8 +39,7 @@ "ਐਪਾਂ ਨੂੰ ਲੋਡ ਕੀਤਾ ਜਾ ਰਿਹਾ ਹੈ..." "\"%1$s\" ਨਾਲ ਮੇਲ ਖਾਂਦੀਆਂ ਕੋਈ ਐਪਾਂ ਨਹੀਂ ਮਿਲੀਆਂ" "ਹੋਰ ਐਪਾਂ ਖੋਜੋ" - - + "ਐਪ" "ਸੂਚਨਾਵਾਂ" "ਕੋਈ ਸ਼ਾਰਟਕੱਟ ਚੁਣਨ ਲਈ ਸਪੱਰਸ਼ ਕਰਕੇ ਦਬਾਈ ਰੱਖੋ।" "ਕੋਈ ਸ਼ਾਰਟਕੱਟ ਚੁਣਨ ਲਈ ਡਬਲ ਟੈਪ ਕਰਕੇ ਦਬਾਈ ਰੱਖੋ ਜਾਂ ਵਿਉਂਤੀਆਂ ਕਾਰਵਾਈਆਂ ਵਰਤੋ।" @@ -66,7 +65,10 @@ "ਇਹ ਇੱਕ ਸਿਸਟਮ ਐਪ ਹੈ ਅਤੇ ਇਸਨੂੰ ਅਣਇੰਸਟੌਲ ਨਹੀਂ ਕੀਤਾ ਜਾ ਸਕਦਾ।" "ਬਿਨਾਂ ਨਾਮ ਦਿੱਤਾ ਫੋਲਡਰ" "%1$s ਨੂੰ ਅਯੋਗ ਬਣਾਇਆ ਗਿਆ" - + + %1$s ਦੀ %2$d ਸੂਚਨਾ + %1$s ਦੀਆਂ %2$d ਸੂਚਨਾਵਾਂ + "ਸਫ਼ਾ %2$d ਦਾ %1$d" "ਹੋਮ ਸਕ੍ਰੀਨ %2$d ਦੀ %1$d" "ਨਵਾਂ ਹੋਮ ਸਕ੍ਰੀਨ ਸਫ਼ਾ" @@ -82,17 +84,13 @@ "ਤੁਹਾਡੇ ਪ੍ਰਸ਼ਾਸਕ ਦੁਆਰਾ ਅਯੋਗ ਬਣਾਈ ਗਈ" "ਹੋਮ ਸਕ੍ਰੀਨ ਨੂੰ ਘੁੰਮਾਉਣ ਦੀ ਆਗਿਆ ਦਿਓ" "ਜਦੋਂ ਫ਼ੋਨ ਘੁੰਮਾਇਆ ਜਾਂਦਾ ਹੈ" - - - - - - + "ਸੂਚਨਾ ਬਿੰਦੂ" + "ਚਾਲੂ" + "ਬੰਦ" "ਸੂਚਨਾ ਪਹੁੰਚ ਲੋੜੀਂਦੀ ਹੈ" "ਸੂਚਨਾ ਬਿੰਦੂਆਂ ਦਿਖਾਉਣ ਲਈ, %1$s ਲਈ ਐਪ ਸੂਚਨਾਵਾਂ ਚਾਲੂ ਕਰੋ" "ਸੈਟਿੰਗਾਂ ਬਦਲੋ" - - + "ਸੂਚਨਾ ਬਿੰਦੂ ਦਿਖਾਓ" "ਹੋਮ ਸਕ੍ਰੀਨ \'ਤੇ ਪ੍ਰਤੀਕ ਸ਼ਾਮਲ ਕਰੋ" "ਨਵੀਆਂ ਐਪਾਂ ਲਈ" "ਪ੍ਰਤੀਕ ਦੀ ਆਕ੍ਰਿਤੀ ਬਦਲੋ" diff --git a/res/values-pl/strings.xml b/res/values-pl/strings.xml index a4a6e98379..242d00dd39 100644 --- a/res/values-pl/strings.xml +++ b/res/values-pl/strings.xml @@ -39,8 +39,7 @@ "Ładuję aplikacje…" "Nie znaleziono aplikacji pasujących do zapytania „%1$s”" "Wyszukaj więcej aplikacji" - - + "Aplikacja" "Powiadomienia" "Kliknij i przytrzymaj, by wybrać skrót." "Kliknij dwukrotnie i przytrzymaj, by wybrać skrót lub użyć działań niestandardowych." @@ -66,7 +65,12 @@ "To aplikacja systemowa i nie można jej odinstalować." "Folder bez nazwy" "Aplikacja %1$s jest wyłączona" - + + %1$s%2$d powiadomienia + %1$s%2$d powiadomień + %1$s%2$d powiadomienia + %1$s%2$d powiadomienie + "Strona %1$d z %2$d" "Ekran główny %1$d z %2$d" "Nowa strona ekranu głównego" @@ -82,17 +86,13 @@ "Funkcja wyłączona przez administratora" "Zezwalaj na obrót ekranu głównego" "Po obróceniu telefonu" - - - - - - + "Plakietki z powiadomieniami" + "Włączono" + "Wyłączono" "Wymagany jest dostęp do powiadomień" "Aby pokazać plakietki z powiadomieniami, włącz powiadomienia aplikacji %1$s" "Zmień ustawienia" - - + "Pokaż plakietki z powiadomieniami" "Dodaj ikonę do ekranu głównego" "W przypadku nowych aplikacji" "Zmień kształt ikon" diff --git a/res/values-pt/strings.xml b/res/values-pt/strings.xml index 39bd520fcc..1e89abe4b7 100644 --- a/res/values-pt/strings.xml +++ b/res/values-pt/strings.xml @@ -39,8 +39,7 @@ "Carregando apps…" "Nenhum app encontrado que corresponda a \"%1$s\"" "Pesquisar mais apps" - - + "App" "Notificações" "Toque e segure para selecionar um atalho." "Toque duas vezes na tela e segure para selecionar um atalho ou usar ações personalizadas." @@ -66,7 +65,10 @@ "Este é um app do sistema e não pode ser desinstalado." "Pasta sem nome" "%1$s desativado" - + + O app %1$stem %2$d notificação + O app %1$stem %2$d notificações + "Página %1$d de %2$d" "Tela inicial %1$d de %2$d" "Nova página na tela inicial" @@ -82,17 +84,13 @@ "Desativado pelo administrador" "Permitir rotação da tela inicial" "Quando o smartphone for girado" - - - - - - + "Pontos de notificação" + "Ativado" + "Desativado" "Acesso a notificações necessário" "Para mostrar pontos de notificação, ative as notificações de app para %1$s" "Alterar configurações" - - + "Mostrar pontos de notificação" "Adicionar ícone à tela inicial" "Para novos apps" "Alterar forma de ícones" diff --git a/res/values-ro/strings.xml b/res/values-ro/strings.xml index c66016af4b..7135f62cca 100644 --- a/res/values-ro/strings.xml +++ b/res/values-ro/strings.xml @@ -39,8 +39,7 @@ "Se încarcă aplicații…" "Nu s-a găsit nicio aplicație pentru „%1$s\"" "Căutați mai multe aplicații" - - + "Aplicație" "Notificări" "Atingeți lung pentru a selecta o comandă rapidă." "Atingeți lung pentru a selecta o comandă rapidă sau folosiți acțiuni personalizate." @@ -66,7 +65,11 @@ "Aceasta este o aplicație de sistem și nu poate fi dezinstalată." "Dosar fără nume" "S-a dezactivat %1$s" - + + %1$s are %2$d notificări + %1$s​ are %2$d de notificări + %1$s are %2$d notificare + "Pagina %1$d din %2$d" "Ecranul de pornire %1$d din %2$d" "Pagină nouă pe ecranul de pornire" @@ -82,17 +85,13 @@ "Dezactivată de administrator" "Permiteți rotirea ecranului de pornire" "Când telefonul este rotit" - - - - - - + "Puncte de notificare" + "Activate" + "Dezactivate" "Este necesar accesul la notificări" "Pentru a afișa punctele de notificare, activați notificările din aplicație pentru %1$s" "Modificați setările" - - + "Afișați punctele de notificare" "Adaugă pictograme în ecranul de pornire" "Pentru aplicații noi" "Schimbați forma pictogramei" diff --git a/res/values-ru/strings.xml b/res/values-ru/strings.xml index 7e73dd6e16..b4d1664f9d 100644 --- a/res/values-ru/strings.xml +++ b/res/values-ru/strings.xml @@ -39,8 +39,7 @@ "Загрузка приложений…" "По запросу \"%1$s\" ничего не найдено" "Искать другие приложения" - - + "Приложение" "Уведомления" "Нажмите и удерживайте, чтобы выбрать ярлык." "Нажмите дважды и удерживайте, чтобы выбрать ярлык или использовать специальные действия." @@ -66,7 +65,12 @@ "Это системное приложение, его нельзя удалить." "Папка без названия" "Приложение %1$s отключено" - + + В приложении \"%1$s\" %2$d уведомление + В приложении \"%1$s\" %2$d уведомления + В приложении \"%1$s\" %2$d уведомлений + В приложении \"%1$s\" %2$d уведомления + "Стр. %1$d из %2$d" "Главный экран %1$d из %2$d" "Новый экран" @@ -82,17 +86,13 @@ "Функция отключена администратором" "Разрешить поворачивать главный экран" "Когда телефон повернут" - - - - - - + "Значки уведомлений" + "Включены" + "Отключены" "Нет доступа к уведомлениям" "Чтобы показывать значки уведомлений, включите уведомления в приложении \"%1$s\"" "Изменить настройки" - - + "Показывать значки уведомлений" "Добавлять значки" "Добавлять значки установленных приложений на главный экран" "Изменить форму значков" diff --git a/res/values-si/strings.xml b/res/values-si/strings.xml index b175f890bb..af428a9ed7 100644 --- a/res/values-si/strings.xml +++ b/res/values-si/strings.xml @@ -39,8 +39,7 @@ "යෙදුම් පූරණය වෙමින්…" "\"%1$s\" සමග ගැළපෙන යෙදුම් හමු නොවිණි" "තව යෙදුම් සඳහා සොයන්න" - - + "යෙදුම" "දැනුම්දීම්" "කෙටි මගක් තෝරා ගැනීමට ස්පර්ශ කර අල්ලාගෙන සිටින්න." "විජට් එකක් තෝරා ගැනීමට හෝ අභිරුචි භාවිත කිරීමට දෙවරක් තට්ටු කර අල්ලා ගෙන සිටින්න." @@ -66,7 +65,10 @@ "මෙය පද්ධති යෙදුමක් වන අතර අස්ථාපනය කළ නොහැක." "නම් නොකළ ෆෝල්ඩරය" "%1$s අබල කෙරිණි" - + + %1$s, දැනුම්දීම් %2$dක් ඇත + %1$s, දැනුම්දීම් %2$dක් ඇත + "%2$d හි %1$d පිටුව" "මුල් පිටු තිරය %2$d හි %1$d" "නව මුල් පිටුව" @@ -82,17 +84,13 @@ "ඔබගේ පරිපාලක විසින් අබල කරන ලදී" "මුල් පිටු තිරය කරකැවීමට ඉඩ දෙන්න" "දුරකථනය කරකවන විට" - - - - - - + "දැනුම්දීම් තිත්" + "ක්‍රියාත්මකයි" + "ක්‍රියාවිරහිතයි" "දැනුම්දීම් ප්‍රවේශය අවශ්‍යයි" "දැනුම්දීම් තිත් පෙන්වීමට, %1$s සඳහා යෙදුම් දැනුම්දීම් සබල කරන්න" "සැකසීම් වෙනස් කරන්න" - - + "දැනුම්දීම් තිත් පෙන්වන්න" "මුල් පිටු තිරය වෙත අයිකනය එක් කරන්න" "නව යෙදුම් සඳහා" "නිරූපක හැඩය වෙනස් කරන්න" diff --git a/res/values-sk/strings.xml b/res/values-sk/strings.xml index af66bf731f..b7014b2212 100644 --- a/res/values-sk/strings.xml +++ b/res/values-sk/strings.xml @@ -39,8 +39,7 @@ "Načítavajú sa aplikácie…" "Nenašli sa žiadne aplikácie zodpovedajúce dopytu %1$s" "Hľadať ďalšie aplikácie" - - + "Aplikácia" "Upozornenia" "Skratku pridáte pridržaním." "Skratku pridáte dvojitým klepnutím a pridržaním alebo pomocou vlastných akcií." @@ -66,7 +65,12 @@ "Toto je systémová aplikácia a nedá sa odinštalovať." "Nepomenovaný priečinok" "Aplikácia %1$s je deaktivovaná" - + + %1$s, má %2$d upozornenia + %1$s, has %2$d notifications + %1$s, má %2$d upozornení + %1$s, má %2$d upozornenie + "Stránka %1$d z %2$d" "Plocha %1$d z %2$d" "Nová stránka plochy" @@ -82,17 +86,13 @@ "Zakázané vaším správcom" "Povoliť otáčanie plochy" "Pri otočení telefónu" - - - - - - + "Bodky upozornení" + "Zapnuté" + "Vypnuté" "Vyžaduje sa prístup k upozorneniam" "Ak chcete, aby sa zobrazovali bodky upozornení, zapnite upozornenia aplikácie %1$s" "Zmeniť nastavenia" - - + "Zobrazovať bodky upozornení" "Pridať ikonu na plochu" "Pri inštalácii novej aplikácie" "Zmeniť tvar ikony" diff --git a/res/values-sl/strings.xml b/res/values-sl/strings.xml index 4f5fc1214d..891fd1f906 100644 --- a/res/values-sl/strings.xml +++ b/res/values-sl/strings.xml @@ -39,8 +39,7 @@ "Nalaganje aplikacij …" "Ni aplikacij, ki bi ustrezale poizvedbi »%1$s«" "Iskanje več aplikacij" - - + "Aplikacija" "Obvestila" "Pridržite bližnjico, da jo izberete." "Dvakrat se dotaknite bližnjice in jo pridržite, da jo izberete, ali pa uporabite dejanja po meri." @@ -66,7 +65,12 @@ "To je sistemska aplikacija in je ni mogoče odstraniti." "Neimenovana mapa" "Aplikacija %1$s je onemogočena" - + + %1$s ima %2$d obvestilo + %1$s ima %2$d obvestili + %1$s ima %2$d obvestila + %1$s ima %2$d obvestil + "Stran %1$d od %2$d" "Začetni zaslon %1$d od %2$d" "Nova stran na začetnem zaslonu" @@ -82,17 +86,13 @@ "Onemogočil skrbnik." "Omogočanje sukanja začetnega zaslona" "Ko se telefon zasuka" - - - - - - + "Obvestilne pike" + "Vklopljeno" + "Izklopljeno" "Potreben je dostop do obvestil" "Za prikaz obvestilnih pik vklopite obvestila aplikacije %1$s" "Spremeni nastavitve" - - + "Pokaži obvestilne pike" "Dodaj ikono na začetni zaslon" "Za nove aplikacije" "Spremeni obliko ikon" diff --git a/res/values-sq/strings.xml b/res/values-sq/strings.xml index ad53e119c0..dd657d7f26 100644 --- a/res/values-sq/strings.xml +++ b/res/values-sq/strings.xml @@ -39,8 +39,7 @@ "Po ngarkon aplikacionet..." "Nuk u gjet asnjë aplikacion që përputhet me \"%1$s\"" "Kërko për më shumë aplikacione" - - + "Aplikacioni" "Njoftimet" "Prek dhe mbaj prekur për të zgjedhur një shkurtore." "Prek dy herë dhe mbaj prekur për të zgjedhur një shkurtore ose për të përdorur veprimet e personalizuara." @@ -66,7 +65,10 @@ "Ky është aplikacion sistemi dhe nuk mund të çinstalohet." "Dosje e paemërtuar" "%1$s u çaktivizua" - + + %1$s, ka %2$d njoftime + %1$s, ka %2$d njoftim + "Faqja: %1$d nga gjithsej %2$d" "Ekrani bazë: %1$d nga gjithsej %2$d" "Faqja e ekranit të ri kryesor" @@ -82,17 +84,13 @@ "Çaktivizuar nga administratori" "Lejo rrotullimin e ekranit kryesor" "Kur telefoni rrotullohet" - - - - - - + "Pikat e njoftimeve" + "Aktiv" + "Joaktiv" "Nevojitet qasja në njoftime" "Për të shfaqur \"Pikat e njoftimeve\", aktivizo njoftimet e aplikacionit për %1$s" "Ndrysho cilësimet" - - + "Shfaq pikat e njoftimeve" "Shto ikonë në ekranin bazë" "Për aplikacionet e reja" "Ndrysho formën e ikonës" diff --git a/res/values-sr/strings.xml b/res/values-sr/strings.xml index d68696a941..c16d32f26f 100644 --- a/res/values-sr/strings.xml +++ b/res/values-sr/strings.xml @@ -39,8 +39,7 @@ "Апликације се учитавају…" "Није пронађена ниједна апликација за „%1$s“" "Претражи још апликација" - - + "Апликација" "Обавештења" "Додирните и задржите да бисте изабрали пречицу." "Двапут додирните и задржите да бисте изабрали пречицу или користите прилагођене радње." @@ -66,7 +65,11 @@ "Ово је системска апликација и не може да се деинсталира." "Неименовани директоријум" "Апликација %1$s је онемогућена" - + + %1$s, има %2$d обавештење + %1$s, има %2$d обавештења + %1$s, има %2$d обавештења + "%1$d. страница од %2$d" "%1$d. почетни екран од %2$d" "Нова страница почетног екрана" @@ -82,17 +85,13 @@ "Администратор је онемогућио" "Дозволи ротацију почетног екрана" "Када се телефон ротира" - - - - - - + "Тачке за обавештења" + "Укључено" + "Искључено" "Потребан је приступ за обавештења" "Да бисте приказали тачке за обавештења, укључите обавештења за апликацију %1$s" "Промените подешавања" - - + "Приказуј тачке за обавештења" "Додај икону на почетни екран" "За нове апликације" "Промените облик икона" diff --git a/res/values-sv/strings.xml b/res/values-sv/strings.xml index 7c4f41f035..feb087c91a 100644 --- a/res/values-sv/strings.xml +++ b/res/values-sv/strings.xml @@ -39,8 +39,7 @@ "Läser in appar …" "Inga appar som matchar %1$s hittades" "Sök efter fler appar" - - + "App" "Aviseringar" "Tryck länge om du vill ta upp en genväg." "Tryck snabbt två gånger och håll kvar om du vill ta upp en genväg eller använda anpassade åtgärder." @@ -66,7 +65,10 @@ "Det här är en systemapp som inte kan avinstalleras." "Namnlös mapp" "%1$s har inaktiverats" - + + %1$s har %2$d aviseringar + %1$s har %2$d avisering + "Sidan %1$d av %2$d" "Startskärmen %1$d av %2$d" "Ny sida på startskärmen" @@ -82,17 +84,13 @@ "Inaktiverat av administratören" "Tillåt rotering av startskärmen" "När mobilen vrids" - - - - - - + "Aviseringsprickar" + "På" + "Av" "Åtkomst till aviseringar krävs" "Aktivera appaviseringar för %1$s om du vill att aviseringsprickar ska visas" "Ändra inställningar" - - + "Visa aviseringsprickar" "Lägg till ikonen på startskärmen" "För nya appar" "Ändra form på ikoner" diff --git a/res/values-sw/strings.xml b/res/values-sw/strings.xml index b6bdbd3fb7..99d3008914 100644 --- a/res/values-sw/strings.xml +++ b/res/values-sw/strings.xml @@ -39,8 +39,7 @@ "Inapakia programu..." "Haikupata programu zozote zinazolingana na \"%1$s\"" "Tafuta programu zaidi" - - + "Programu" "Arifa" "Gusa na ushikilie ili uchague njia ya mkato." "Gusa mara mbili na ushikilie ili uchague njia ya mkato au utumie vitendo maalum." @@ -66,7 +65,10 @@ "Hii ni programu ya mfumo na haiwezi kuondolewa." "Folda isiyo na jina" "%1$s imezimwa" - + + %1$s, ina arifa %2$d + %1$s, ina arifa %2$d + "Ukurasa%1$d wa %2$d" @@ -84,17 +86,13 @@ "Imezimwa na msimamizi wako" "Ruhusu kuzungusha skrini ya Kwanza" "Simu inapozungushwa" - - - - - - + "Vitone vya arifa" + "Imewashwa" + "Imezimwa" "Inahitaji idhini ya kufikia arifa" "Ili kuonyesha Vitone vya Arifa, washa kipengele cha arifa za programu katika %1$s" "Badilisha mipangilio" - - + "Onyesha vitone vya arifa" "Ongeza aikoni kwenye Skrini ya kwanza" "Kwa ajili ya programu mpya" "Badilisha umbo la aikoni" diff --git a/res/values-ta/strings.xml b/res/values-ta/strings.xml index b25d1d21a3..43ff0977cd 100644 --- a/res/values-ta/strings.xml +++ b/res/values-ta/strings.xml @@ -39,8 +39,7 @@ "பயன்பாடுகளை ஏற்றுகிறது…" "\"%1$s\" உடன் பொருந்தும் பயன்பாடுகள் இல்லை" "கூடுதல் பயன்பாடுகளைத் தேடு" - - + "ஆப்ஸ்" "அறிவிப்புகள்" "ஷார்ட்கட்டைச் சேர்க்க, தொட்டு பிடித்திருக்கவும்." "ஷார்ட்கட்டைச் சேர்க்க, இருமுறை தட்டிப் பிடித்திருக்கவும் (அ) தனிப்பயன் செயல்களைப் பயன்படுத்தவும்." @@ -66,7 +65,10 @@ "இது அமைப்பு பயன்பாடு என்பதால் நிறுவல் நீக்கம் செய்ய முடியாது." "பெயரிடப்படாத கோப்புறை" "%1$s முடக்கப்பட்டது" - + + %1$s ஆப்ஸில் %2$d அறிவிப்புகள் வந்துள்ளன + %1$s ஆப்ஸில் %2$d அறிவிப்பு வந்துள்ளது + "பக்கம் %1$d / %2$d" "முகப்புத் திரை %1$d of %2$d" "புதிய முகப்புத் திரை பக்கம்" @@ -82,17 +84,13 @@ "உங்கள் நிர்வாகி முடக்கியுள்ளார்" "முகப்புத் திரை சுழற்சியை அனுமதி" "மொபைலைச் சுழற்றும் போது" - - - - - - + "அறிவிப்புப் புள்ளிகள்" + "ஆன்" + "ஆஃப்" "அறிவிப்பிற்கான அணுகல் தேவை" "அறிவிப்புப் புள்ளிகளைக் காட்ட, %1$s இன் பயன்பாட்டு அறிவிப்புகளை இயக்கவும்" "அமைப்புகளை மாற்று" - - + "அறிவிப்புப் புள்ளிகளைக் காட்டு" "முகப்புத் திரையில் ஐகானைச் சேர்" "புதிய பயன்பாடுகளுக்கு" "ஐகான் வடிவத்தை மாற்று" diff --git a/res/values-te/strings.xml b/res/values-te/strings.xml index 4ec35a9e77..04f253936f 100644 --- a/res/values-te/strings.xml +++ b/res/values-te/strings.xml @@ -39,8 +39,7 @@ "అప్లికేషన్‌లను లోడ్ చేస్తోంది…" "\"%1$s\"కి సరిపోలే అప్లికేషన్‌లేవీ కనుగొనబడలేదు" "మరిన్ని యాప్‌ల కోసం వెతుకు" - - + "యాప్" "నోటిఫికేషన్‌లు" "షార్ట్‌కట్‌ని ఎంచుకోవడం కోసం నొక్కి, పట్టుకోండి." "రెండుసార్లు నొక్కి, పట్టుకోవడం ద్వారా షార్ట్‌కట్‌ని ఎంచుకోండి లేదా అనుకూల చర్యలను ఉపయోగించండి." @@ -66,7 +65,10 @@ "ఇది సిస్టమ్ యాప్ మరియు దీన్ని అన్‌ఇన్‌స్టాల్ చేయడం సాధ్యపడదు." "పేరు లేని ఫోల్డర్" "%1$s నిలిపివేయబడింది" - + + %1$s, నుంచి %2$d నోటిఫికేషన్‌లు ఉన్నాయి + %1$s, నుంచి %2$d నోటిఫికేషన్ ఉంది + "%2$dలో %1$dవ పేజీ" "%2$dలో %1$dవ హోమ్ స్క్రీన్" "కొత్త హోమ్ స్క్రీన్ పేజీ" @@ -82,17 +84,13 @@ "మీ నిర్వాహకులు నిలిపివేసారు" "హోమ్ స్క్రీన్ భ్రమణాన్ని అనుమతించండి" "ఫోన్‌‌ను తిప్పినప్పుడు" - - - - - - + "నోటిఫికేషన్ డాట్‌లు" + "ఆన్" + "ఆఫ్" "నోటిఫికేషన్ యాక్సెస్ అవసరం" "నోటిఫికేషన్ డాట్‌లను చూపించడానికి %1$sకు యాప్ నోటిఫికేషన్‌లను ఆన్ చేయండి" "సెట్టింగ్‌లను మార్చు" - - + "నోటిఫికేషన్ డాట్‌లను చూపు" "హోమ్ స్క్రీన్‌కి చిహ్నాన్ని జోడించు" "కొత్త యాప్‌ల కోసం" "చిహ్న ఆకారాన్ని మార్చు" diff --git a/res/values-th/strings.xml b/res/values-th/strings.xml index 4895c376f9..8473ec0b36 100644 --- a/res/values-th/strings.xml +++ b/res/values-th/strings.xml @@ -39,8 +39,7 @@ "กำลังโหลดแอป…" "ไม่พบแอปที่ตรงกับ \"%1$s\"" "ค้นหาแอปเพิ่มเติม" - - + "แอป" "การแจ้งเตือน" "แตะค้างไว้เพื่อเลือกทางลัด" "แตะสองครั้งค้างไว้เพื่อเลือกทางลัดหรือใช้การกระทำที่กำหนดเอง" @@ -66,7 +65,10 @@ "นี่เป็นแอประบบและไม่สามารถถอนการติดตั้งได้" "โฟลเดอร์ที่ไม่มีชื่อ" "ปิดใช้ %1$s" - + + %1$s มีการแจ้งเตือน %2$d รายการ + %1$s มีการแจ้งเตือน %2$d รายการ + "หน้า %1$d จาก %2$d" "หน้าจอหลัก %1$d จาก %2$d" "หน้าใหม่ในหน้าจอหลัก" @@ -82,17 +84,13 @@ "ปิดใช้โดยผู้ดูแลระบบ" "อนุญาตให้หมุนหน้าจอหลัก" "เมื่อหมุนโทรศัพท์" - - - - - - + "เครื่องหมายจุดแสดงการแจ้งเตือน" + "เปิด" + "ปิด" "ต้องได้รับสิทธิ์เข้าถึงการแจ้งเตือน" "เปิดการแจ้งเตือนแอปของ %1$s เพื่อแสดงจุดแจ้งเตือน" "เปลี่ยนการตั้งค่า" - - + "แสดงเครื่องหมายจุดแสดงการแจ้งเตือน" "เพิ่มไอคอนในหน้าจอหลัก" "สำหรับแอปใหม่" "เปลี่ยนรูปร่างไอคอน" diff --git a/res/values-tl/strings.xml b/res/values-tl/strings.xml index 90f1bf4999..90b0a182d5 100644 --- a/res/values-tl/strings.xml +++ b/res/values-tl/strings.xml @@ -39,8 +39,7 @@ "Naglo-load ng mga app…" "Walang nahanap na app na tumutugma sa \"%1$s\"" "Maghanap ng higit pang mga app" - - + "App" "Mga Notification" "Pindutin nang matagal para kumuha ng shortcut." "I-double tap nang matagal para kumuha ng shortcut o gumamit ng mga custom na pagkilos." @@ -66,7 +65,10 @@ "Isa itong app ng system at hindi maaaring i-uninstall." "Walang Pangalang Folder" "Naka-disable ang %1$s" - + + May %2$d (na) notification ang %1$s + May %2$d (na) notification ang %1$s + "Pahina %1$d ng %2$d" "Home screen %1$d ng %2$d" "Bagong page ng home screen" @@ -82,17 +84,13 @@ "Na-disable ng iyong admin" "Payagan ang pag-rotate ng Home screen" "Kailan maro-rotate ang telepono" - - - - - - + "Mga notification dot" + "Naka-on" + "Naka-off" "Kinakailangan ng access sa notification" "Upang ipakita ang Mga Notification Dot, i-on ang mga notification ng app para sa %1$s" "Baguhin ang mga setting" - - + "Ipakita ang mga notification dot" "Idagdag ang icon sa Home screen" "Para sa mga bagong app" "Baguhin ang hugis ng icon" diff --git a/res/values-tr/strings.xml b/res/values-tr/strings.xml index 34171fc00a..88b85f7fc3 100644 --- a/res/values-tr/strings.xml +++ b/res/values-tr/strings.xml @@ -39,8 +39,7 @@ "Uygulamalar yükleniyor…" "\"%1$s\" ile eşleşen uygulama bulunamadı" "Başka uygulamalar ara" - - + "Uygulama" "Bildirimler" "Kısayol seçmek için dokunun ve basılı tutun." "Bir kısayolu seçmek veya özel işlemleri kullanmak için iki kez dokunun ve basılı tutun." @@ -66,7 +65,10 @@ "Bu bir sistem uygulamasıdır ve yüklemesi kaldırılamaz." "Adsız Klasör" "%1$s devre dışı" - + + %1$s uygulamasının %2$d bildirimi var + %1$s uygulamasının %2$d bildirimi var + "Sayfa %1$d / %2$d" "Ana ekran %1$d / %2$d" "Yeni ana ekran sayfası" @@ -82,17 +84,13 @@ "Yöneticiniz tarafından devre dışı bırakıldı" "Ana ekranı döndürmeye izin ver" "Telefon döndürüldüğünde" - - - - - - + "Bildirim noktaları" + "Açık" + "Kapalı" "Bildirim erişimi gerekli" "Bildirim Noktaları\'nı göstermek için %1$s uygulamasının bildirimlerini açın" "Ayarları değiştir" - - + "Bildirim noktalarını göster" "Ana ekrana simge ekle" "Yeni uygulamalar için" "Simge şeklini değiştir" diff --git a/res/values-uk/strings.xml b/res/values-uk/strings.xml index 17dac15971..e62cb52c23 100644 --- a/res/values-uk/strings.xml +++ b/res/values-uk/strings.xml @@ -39,8 +39,7 @@ "Завантаження додатків…" "Немає додатків для запиту \"%1$s\"" "Шукати ще додатки" - - + "Додаток" "Сповіщення" "Натисніть і втримуйте, щоб вибрати ярлик." "Двічі натисніть і втримуйте, щоб вибрати ярлик, або виконайте іншу дію." @@ -66,7 +65,12 @@ "Це системна програма, її неможливо видалити." "Папка без назви" "%1$s вимкнено" - + + Додаток %1$s має %2$d сповіщення + Додаток %1$s має %2$d сповіщення + Додаток %1$s має %2$d сповіщень + Додаток %1$s має %2$d сповіщення + "Сторінка %1$d з %2$d" "Головний екран %1$d з %2$d" "Нова сторінка головного екрана" @@ -82,17 +86,13 @@ "Вимкнув адміністратор" "Дозволити обертання головного екрана" "Коли телефон обертається" - - - - - - + "Значки сповіщень" + "Увімкнено" + "Вимкнено" "Потрібен доступ до сповіщень" "Щоб показувати значки сповіщень, увімкніть сповіщення в додатку %1$s" "Змінити налаштування" - - + "Показувати значки сповіщень" "Додати значок на головний екран" "Для нових додатків" "Змінити форму значка" @@ -104,7 +104,7 @@ "Сльоза" "Змінюється форма значка" "Невідомо" - "Вилучити значок" + "Прибрати" "Шукати" "Цей додаток не встановлено" "Додаток для цього значка не встановлено. Можна видалити значок або знайти додаток і встановити його вручну." diff --git a/res/values-ur/strings.xml b/res/values-ur/strings.xml index e8b6b6b7ff..fde98c90f5 100644 --- a/res/values-ur/strings.xml +++ b/res/values-ur/strings.xml @@ -39,8 +39,7 @@ "ایپس لوڈ کی جا رہی ہیں…" "\"%1$s\" سے مماثل کوئی ایپس نہیں ملیں" "مزید ایپس تلاش کریں" - - + "ایپ" "اطلاعات" "ایک شارٹ کٹ منتخب کرنے کیلئے ٹچ کر کے دبائے رکھیں۔" "ایک شارٹ کٹ منتخب کرنے یا حسب ضرورت کارروائیاں استعمال کرنے کیلئے دو بار تھپتھپائیں اور دبائے رکھیں۔" @@ -66,7 +65,10 @@ "یہ ایک سسٹم ایپ ہے اور اسے اَن انسٹال نہیں کیا جا سکتا ہے۔" "بلا نام فولڈر" "%1$s غیر فعال ہے" - + + %1$s میں%2$d اطلاعات ہیں + %1$s میں%2$d اطلاع ہے + "‏صفحہ ‎%1$d از ‎%2$d" "‏ہوم اسکرین ‎%1$d از ‎%2$d" "نیا ہوم اسکرین صفحہ" @@ -82,17 +84,13 @@ "آپ کے منتظم کی طرف سے غیر فعال کر دیا گیا" "ہوم اسکرین گھمانے کی اجازت دیں" "جب فون گھمایا جاتا ہے" - - - - - - + "اطلاعاتی ڈاٹس" + "آن ہے" + "آف ہے" "اطلاعاتی رسائی درکار ہے" "اطلاعاتی ڈاٹس دکھانے کی خاطر %1$s کیلئے ایپ کی اطلاعات آن کریں" "ترتیبات تبدیل کریں" - - + "اطلاعاتی ڈاٹس دکھائیں" "آئیکن کو ہوم اسکرین میں شامل کریں" "نئی ایپس کیلئے" "آئیکن کی شکل تبدیل کریں" diff --git a/res/values-uz/strings.xml b/res/values-uz/strings.xml index 3165726977..015dcda745 100644 --- a/res/values-uz/strings.xml +++ b/res/values-uz/strings.xml @@ -39,8 +39,7 @@ "Ilovalar yuklanmoqda…" "“%1$s” bilan mos hech qanday ilova topilmadi" "Boshqa ilovalarni qidirish" - - + "Ilova" "Bildirishnomalar" "Yorliqni tanlab olish uchun bosib turing." "Ikki marta bosib va bosib turgan holatda yorliqni tanlang yoki maxsus amaldan foydalaning." @@ -66,7 +65,10 @@ "Bu tizim ilovasi, shuning uchun o‘chirib bo‘lmaydi." "Nomsiz jild" "%1$s ilovasi o‘chirib qo‘yildi" - + + %1$s ilovasida %2$d ta bildirishnoma bor + %1$s ilovasida %2$d ta bildirishnoma bor + "%2$ddan %1$d ta sahifa" "Uy ekrani %2$ddan %1$d" "Yangi bosh ekran sahifasi" @@ -82,17 +84,13 @@ "Administrator tomonidan o‘chirilgan" "Asosiy ekranni aylantirishga ruxsat berish" "Telefon burilganda" - - - - - - + "Bildirishnoma belgilari" + "Yoniq" + "Yoqilmagan" "Bildirishnomalarga ruxsat berilmagan" "Bildirishnoma belgilarini ko‘rsatish uchun %1$s ilovasida bildirishnomalarni yoqing" "Sozlamalarni o‘zgartirish" - - + "Bildirishnoma belgilarini chiqarish" "Bosh ekranga ikonka chiqarish" "Yangi o‘rnatilgan ilovalar ikonkasini bosh ekranga chiqarish" "Ikonka shaklini o‘zgartirish" diff --git a/res/values-vi/strings.xml b/res/values-vi/strings.xml index 254420d13d..69b17c6836 100644 --- a/res/values-vi/strings.xml +++ b/res/values-vi/strings.xml @@ -39,8 +39,7 @@ "Đang tải ứng dụng…" "Không tìm thấy ứng dụng nào phù hợp với \"%1$s\"" "Tìm kiếm thêm ứng dụng" - - + "Ứng dụng" "Thông báo" "Chạm và giữ để chọn lối tắt." "Nhấn đúp và giữ để chọn lối tắt hoặc sử dụng hành động tùy chỉnh." @@ -66,7 +65,10 @@ "Đây là ứng dụng hệ thống và không thể gỡ cài đặt." "Thư mục chưa đặt tên" "Đã vô hiệu hóa %1$s" - + + %1$s, có %2$d thông báo + %1$s, có %2$d thông báo + "Trang %1$d / %2$d" "Màn hình chính %1$d / %2$d" "Trang màn hình chính mới" @@ -82,17 +84,13 @@ "Bị tắt bởi quản trị viên của bạn" "Cho phép xoay Màn hình chính" "Khi xoay điện thoại" - - - - - - + "Dấu chấm thông báo" + "Bật" + "Tắt" "Cần quyền truy cập thông báo" "Để hiển thị Dấu chấm thông báo, hãy bật thông báo ứng dụng cho %1$s" "Thay đổi cài đặt" - - + "Hiển thị dấu chấm thông báo" "Thêm biểu tượng vào màn hình chính" "Cho ứng dụng mới" "Thay đổi hình dạng biểu tượng" diff --git a/res/values-zh-rCN/strings.xml b/res/values-zh-rCN/strings.xml index f1f3b9759c..cbec49ec9a 100644 --- a/res/values-zh-rCN/strings.xml +++ b/res/values-zh-rCN/strings.xml @@ -39,8 +39,7 @@ "正在加载应用…" "未找到与“%1$s”相符的应用" "搜索更多应用" - - + "应用" "通知" "触摸并按住快捷方式即可选择快捷方式。" "点按两次并按住快捷方式即可选择快捷方式,您也可以使用自定义操作。" @@ -66,7 +65,10 @@ "这是系统应用,无法卸载。" "未命名文件夹" "已停用%1$s" - + + %1$s,有 %2$d 个通知 + %1$s,有 %2$d 个通知 + "第%1$d页,共%2$d页" "主屏幕:第%1$d屏,共%2$d屏" "主屏幕新页面" @@ -82,17 +84,13 @@ "已被您的管理员停用" "允许旋转主屏幕" "手机旋转时" - - - - - - + "通知圆点" + "开启" + "关闭" "需要获取通知使用权" "要显示通知圆点,请开启%1$s的应用通知功能" "更改设置" - - + "显示通知圆点" "将图标添加到主屏幕" "适用于新应用" "更改图标形状" diff --git a/res/values-zh-rHK/strings.xml b/res/values-zh-rHK/strings.xml index 0f70e9ea9d..648b1cc279 100644 --- a/res/values-zh-rHK/strings.xml +++ b/res/values-zh-rHK/strings.xml @@ -39,8 +39,7 @@ "正在載入應用程式…" "找不到與「%1$s」相符的應用程式" "搜尋更多應用程式" - - + "應用程式" "通知" "按住捷徑即可選取。" "連㩒兩下之後繼續㩒住,就可以揀選捷徑或者用自訂嘅操作。" @@ -66,7 +65,10 @@ "這是系統應用程式,無法將其解除安裝。" "未命名的資料夾" "「%1$s」已停用" - + + %1$s,有 %2$d 項通知 + %1$s,有 %2$d 項通知 + "第 %1$d 頁,共 %2$d 頁" "主畫面 %1$d,共 %2$d 個" "新主畫面頁面" @@ -82,17 +84,13 @@ "已由您的管理員停用" "允許主畫面旋轉" "當手機旋轉時" - - - - - - + "通知圓點" + "開啟" + "關閉" "需要獲取通知存取權" "如要顯示「通知圓點」,請開啟「%1$s」的應用程式通知功能" "變更設定" - - + "顯示通知圓點" "將圖示加到主畫面" "適用於新安裝的應用程式" "變更圖示形狀" diff --git a/res/values-zh-rTW/strings.xml b/res/values-zh-rTW/strings.xml index 652679a017..74f2949b39 100644 --- a/res/values-zh-rTW/strings.xml +++ b/res/values-zh-rTW/strings.xml @@ -39,8 +39,7 @@ "正在載入應用程式…" "找不到與「%1$s」相符的應用程式" "搜尋更多應用程式" - - + "應用程式" "通知" "按住捷徑即可選取。" "輕觸兩下並按住捷徑即可選取,你也可以使用自訂動作。" @@ -66,7 +65,10 @@ "這是系統應用程式,不可解除安裝。" "未命名的資料夾" "已停用 %1$s" - + + %1$s,有 %2$d 則通知 + %1$s,有 %2$d 則通知 + "第 %1$d 頁,共 %2$d 頁" "主螢幕:第 %1$d 頁,共 %2$d 頁" "新的主畫面頁面" @@ -82,17 +84,13 @@ "已由你的管理員停用" "允許旋轉主螢幕" "當手機旋轉時" - - - - - - + "通知圓點" + "開啟" + "關閉" "需要取得通知存取權" "如要顯示通知圓點,請開啟「%1$s」的應用程式通知功能" "變更設定" - - + "顯示通知圓點" "將圖示加到主螢幕" "適用於新安裝的應用程式" "變更圖示形狀" diff --git a/res/values-zu/strings.xml b/res/values-zu/strings.xml index 22fba2bc06..89893fc6ff 100644 --- a/res/values-zu/strings.xml +++ b/res/values-zu/strings.xml @@ -39,8 +39,7 @@ "Ilayisha izinhlelo zokusebenza..." "Azikho izinhlelo zokusebenza ezitholiwe ezifana ne-\"%1$s\"" "Sesha izinhlelo zokusebenza eziningi" - - + "Uhlelo lokusebenza" "Izaziso" "Thinta futhi ubambe ukuze ukhethe isinqamuleli." "Thepha kabili uphinde ubambe ukuze uphakamise isinqamuleli noma usebenzise izenzo zangokwezifiso." @@ -66,7 +65,10 @@ "Lolu uhlelo lokusebenza lwesistimu futhi alikwazi ukukhishwa." "Ifolda engenagama" "Kukhutshaziwe %1$s" - + + %1$s, unezaziso ezingu-%2$d + %1$s, unezaziso ezingu-%2$d + "Ikhasi elingu-%1$d kwangu-%2$d" "Isikrini sasekhaya esingu-%1$d se-%2$d" "Ikhasi elisha lesikrini sasekhaya" @@ -82,17 +84,13 @@ "Kukhutshazwe umlawuli wakho" "Vumela ukuphendukiswa kwesikrini sasekhaya" "Uma ifoni iphendukiswa" - - - - - - + "Amacashazi esaziso" + "Vuliwe" + "Valiwe" "Ukufinyelela izaziso kuyadingeka" "Ukuze ubonisa amcashazi esaziso, vula izaziso zohlelo lokusebenza ze-%1$s" "Shintsha izilungiselelo" - - + "Bonisa amacashazi esaziso" "Engeza isithonjana eskrinini sasekhaya" "Kwezinhlelo zokusebenza ezintsha" "Shintsha isimo sesithonjana" diff --git a/res/values/attrs.xml b/res/values/attrs.xml index 29bd3c8879..8c4dd1efdc 100644 --- a/res/values/attrs.xml +++ b/res/values/attrs.xml @@ -93,10 +93,8 @@ - + - - @@ -106,13 +104,21 @@ + + + + + + + + + - - - + + diff --git a/res/values/dimens.xml b/res/values/dimens.xml index 8adae36187..078ce60a62 100644 --- a/res/values/dimens.xml +++ b/res/values/dimens.xml @@ -36,7 +36,7 @@ 2dp 0dp - 80dp + 34dp 0dp diff --git a/res/values/strings.xml b/res/values/strings.xml index 3f01278760..b1eff24578 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -205,23 +205,23 @@ For new apps - - Change icon shape - - on Home screen - - Use system default - - Square - - Squircle - - Circle - - Teardrop + + Change icon shape + + on Home screen + + Use system default + + Square + + Squircle + + Circle + + Teardrop - - Applying icon shape changes + + Applying icon shape changes Unknown diff --git a/res/xml/default_workspace_3x3.xml b/res/xml/default_workspace_3x3.xml index 8d99404504..31376e1d5e 100644 --- a/res/xml/default_workspace_3x3.xml +++ b/res/xml/default_workspace_3x3.xml @@ -68,21 +68,4 @@ - - - - - - - - - - - diff --git a/res/xml/default_workspace_4x4.xml b/res/xml/default_workspace_4x4.xml index 979a1b4c84..bf3c62c77d 100644 --- a/res/xml/default_workspace_4x4.xml +++ b/res/xml/default_workspace_4x4.xml @@ -16,8 +16,48 @@ - - + + + + + + + + + + + + + + + + + + + + + + + + + - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - diff --git a/res/xml/device_profiles.xml b/res/xml/device_profiles.xml index ef6e14506b..88b1bc95ae 100644 --- a/res/xml/device_profiles.xml +++ b/res/xml/device_profiles.xml @@ -17,158 +17,109 @@ - + launcher:defaultLayoutId="@xml/default_workspace_3x3" > - + - + + + + + launcher:numHotseatIcons="4" + launcher:defaultLayoutId="@xml/default_workspace_4x4" > - + - + - + - + - + + + + + launcher:defaultLayoutId="@xml/default_workspace_5x5" > - + - + - + \ No newline at end of file diff --git a/res/xml/dw_phone_hotseat.xml b/res/xml/dw_phone_hotseat.xml deleted file mode 100644 index c691ebc3da..0000000000 --- a/res/xml/dw_phone_hotseat.xml +++ /dev/null @@ -1,70 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/res/xml/dw_tablet_hotseat.xml b/res/xml/dw_tablet_hotseat.xml deleted file mode 100644 index 6fe7f93d82..0000000000 --- a/res/xml/dw_tablet_hotseat.xml +++ /dev/null @@ -1,84 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/res/xml/launcher_preferences.xml b/res/xml/launcher_preferences.xml index be3cb0e28f..7e7220813a 100644 --- a/res/xml/launcher_preferences.xml +++ b/res/xml/launcher_preferences.xml @@ -44,15 +44,6 @@ android:defaultValue="@bool/allow_rotation" android:persistent="true" /> - - notAnimated = ArgumentCaptor.forClass(ArrayList.class); @@ -141,7 +138,7 @@ public class AddWorkspaceItemsTaskTest extends BaseModelUpdateTaskTestCase { assertTrue(animated.getValue().contains(info2)); } - private int setupWorkspaceWithHoles(int startId, int screenId, Rect... holes) { + private int setupWorkspaceWithHoles(int startId, int screenId, Rect... holes) throws Exception { GridOccupancy occupancy = new GridOccupancy(idp.numColumns, idp.numRows); occupancy.markCells(0, 0, idp.numColumns, idp.numRows, true); for (Rect r : holes) { @@ -151,6 +148,7 @@ public class AddWorkspaceItemsTaskTest extends BaseModelUpdateTaskTestCase { existingScreens.add(screenId); screenOccupancy.append(screenId, occupancy); + ExecutorService executor = Executors.newSingleThreadExecutor(); for (int x = 0; x < idp.numColumns; x++) { for (int y = 0; y < idp.numRows; y++) { if (!occupancy.cells[x][y]) { @@ -165,27 +163,19 @@ public class AddWorkspaceItemsTaskTest extends BaseModelUpdateTaskTestCase { info.cellY = y; info.container = LauncherSettings.Favorites.CONTAINER_DESKTOP; bgDataModel.addItem(targetContext, info, false); + + executor.execute(() -> { + ContentWriter writer = new ContentWriter(targetContext); + info.writeToValues(writer); + writer.put(Favorites._ID, info.id); + targetContext.getContentResolver().insert(Favorites.CONTENT_URI, + writer.getValues(targetContext)); + }); } } + + executor.submit(() -> null).get(); + executor.shutdown(); return startId; } - - private void commitScreensToDb() throws Exception { - LauncherSettings.Settings.call(targetContext.getContentResolver(), - LauncherSettings.Settings.METHOD_CREATE_EMPTY_DB); - - Uri uri = LauncherSettings.WorkspaceScreens.CONTENT_URI; - ArrayList ops = new ArrayList<>(); - // Clear the table - ops.add(ContentProviderOperation.newDelete(uri).build()); - int count = existingScreens.size(); - for (int i = 0; i < count; i++) { - ContentValues v = new ContentValues(); - int screenId = existingScreens.get(i); - v.put(LauncherSettings.WorkspaceScreens._ID, screenId); - v.put(LauncherSettings.WorkspaceScreens.SCREEN_RANK, i); - ops.add(ContentProviderOperation.newInsert(uri).withValues(v).build()); - } - targetContext.getContentResolver().applyBatch(LauncherProvider.AUTHORITY, ops); - } } diff --git a/tests/src/com/android/launcher3/model/DbDowngradeHelperTest.java b/robolectric_tests/src/com/android/launcher3/model/DbDowngradeHelperTest.java similarity index 81% rename from tests/src/com/android/launcher3/model/DbDowngradeHelperTest.java rename to robolectric_tests/src/com/android/launcher3/model/DbDowngradeHelperTest.java index 0903ddc16d..a46617ec28 100644 --- a/tests/src/com/android/launcher3/model/DbDowngradeHelperTest.java +++ b/robolectric_tests/src/com/android/launcher3/model/DbDowngradeHelperTest.java @@ -20,14 +20,17 @@ import static junit.framework.Assert.assertFalse; import static junit.framework.Assert.assertNotSame; import static junit.framework.Assert.assertTrue; +import static org.mockito.Matchers.eq; +import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.when; + import android.content.ContentValues; import android.content.Context; +import android.content.res.Resources; import android.database.Cursor; import android.database.sqlite.SQLiteDatabase; import android.database.sqlite.SQLiteOpenHelper; -import androidx.test.InstrumentationRegistry; -import androidx.test.filters.SmallTest; -import androidx.test.runner.AndroidJUnit4; import com.android.launcher3.LauncherProvider; import com.android.launcher3.LauncherProvider.DatabaseHelper; @@ -37,14 +40,15 @@ import com.android.launcher3.R; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; +import org.robolectric.RobolectricTestRunner; +import org.robolectric.RuntimeEnvironment; import java.io.File; /** * Tests for {@link DbDowngradeHelper} */ -@SmallTest -@RunWith(AndroidJUnit4.class) +@RunWith(RobolectricTestRunner.class) public class DbDowngradeHelperTest { private static final String SCHEMA_FILE = "test_schema.json"; @@ -56,35 +60,47 @@ public class DbDowngradeHelperTest { @Before public void setup() { - mContext = InstrumentationRegistry.getTargetContext(); + mContext = RuntimeEnvironment.application; mSchemaFile = mContext.getFileStreamPath(SCHEMA_FILE); mDbFile = mContext.getDatabasePath(DB_FILE); } + @Test + public void testDowngradeSchemaMatchesVersion() throws Exception { + mSchemaFile.delete(); + assertFalse(mSchemaFile.exists()); + DbDowngradeHelper.updateSchemaFile(mSchemaFile, 0, mContext); + assertEquals(LauncherProvider.SCHEMA_VERSION, DbDowngradeHelper.parse(mSchemaFile).version); + } + @Test public void testUpdateSchemaFile() throws Exception { - Context myContext = InstrumentationRegistry.getContext(); - int testResId = myContext.getResources().getIdentifier( - "db_schema_v10", "raw", myContext.getPackageName()); + // Setup mock resources + Resources res = spy(mContext.getResources()); + doAnswer(i ->this.getClass().getResourceAsStream("/db_schema_v10.json")) + .when(res).openRawResource(eq(R.raw.downgrade_schema)); + Context context = spy(mContext); + when(context.getResources()).thenReturn(res); + mSchemaFile.delete(); assertFalse(mSchemaFile.exists()); - DbDowngradeHelper.updateSchemaFile(mSchemaFile, 10, myContext, testResId); + DbDowngradeHelper.updateSchemaFile(mSchemaFile, 10, context); assertTrue(mSchemaFile.exists()); assertEquals(10, DbDowngradeHelper.parse(mSchemaFile).version); // Schema is updated on version upgrade assertTrue(mSchemaFile.setLastModified(0)); - DbDowngradeHelper.updateSchemaFile(mSchemaFile, 11, myContext, testResId); + DbDowngradeHelper.updateSchemaFile(mSchemaFile, 11, context); assertNotSame(0, mSchemaFile.lastModified()); // Schema is not updated when version is same assertTrue(mSchemaFile.setLastModified(0)); - DbDowngradeHelper.updateSchemaFile(mSchemaFile, 10, myContext, testResId); + DbDowngradeHelper.updateSchemaFile(mSchemaFile, 10, context); assertEquals(0, mSchemaFile.lastModified()); // Schema is not updated on version downgrade - DbDowngradeHelper.updateSchemaFile(mSchemaFile, 3, myContext, testResId); + DbDowngradeHelper.updateSchemaFile(mSchemaFile, 3, context); assertEquals(0, mSchemaFile.lastModified()); } @@ -143,8 +159,7 @@ public class DbDowngradeHelperTest { mSchemaFile.delete(); mDbFile.delete(); - DbDowngradeHelper.updateSchemaFile(mSchemaFile, LauncherProvider.SCHEMA_VERSION, mContext, - R.raw.downgrade_schema); + DbDowngradeHelper.updateSchemaFile(mSchemaFile, LauncherProvider.SCHEMA_VERSION, mContext); DatabaseHelper dbHelper = new DatabaseHelper(mContext, null, DB_FILE) { @Override diff --git a/robolectric_tests/src/com/android/launcher3/model/GridSizeMigrationTaskTest.java b/robolectric_tests/src/com/android/launcher3/model/GridSizeMigrationTaskTest.java index 67580ddd8d..d4188aa8e8 100644 --- a/robolectric_tests/src/com/android/launcher3/model/GridSizeMigrationTaskTest.java +++ b/robolectric_tests/src/com/android/launcher3/model/GridSizeMigrationTaskTest.java @@ -1,5 +1,7 @@ package com.android.launcher3.model; +import static com.android.launcher3.model.GridSizeMigrationTask.getWorkspaceScreenIds; + import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; @@ -10,7 +12,6 @@ import android.database.Cursor; import android.graphics.Point; import com.android.launcher3.InvariantDeviceProfile; -import com.android.launcher3.LauncherModel; import com.android.launcher3.LauncherProvider; import com.android.launcher3.LauncherSettings; import com.android.launcher3.config.FlagOverrideRule; @@ -55,7 +56,6 @@ public class GridSizeMigrationTaskTest { @Before public void setUp() { - mValidPackages = new HashSet<>(); mValidPackages.add(TEST_PACKAGE); mIdp = new InvariantDeviceProfile(); @@ -307,11 +307,6 @@ public class GridSizeMigrationTaskTest { LauncherSettings.Settings.call(mContext.getContentResolver(), LauncherSettings.Settings.METHOD_NEW_SCREEN_ID); - ContentValues v = new ContentValues(); - v.put(LauncherSettings.WorkspaceScreens._ID, screenId); - v.put(LauncherSettings.WorkspaceScreens.SCREEN_RANK, i); - mContext.getContentResolver().insert(LauncherSettings.WorkspaceScreens.CONTENT_URI, v); - ids[i] = new int[typeArray[i].length][]; for (int y = 0; y < typeArray[i].length; y++) { ids[i][y] = new int[typeArray[i][y].length]; @@ -326,7 +321,6 @@ public class GridSizeMigrationTaskTest { } } - IntArray allScreens = LauncherModel.loadWorkspaceScreensDb(mContext); return ids; } @@ -336,7 +330,7 @@ public class GridSizeMigrationTaskTest { * represent the workspace grid. */ private void verifyWorkspace(int[][][] ids) { - IntArray allScreens = LauncherModel.loadWorkspaceScreensDb(mContext); + IntArray allScreens = getWorkspaceScreenIds(mContext); assertEquals(ids.length, allScreens.size()); int total = 0; diff --git a/src/com/android/launcher3/DeviceProfile.java b/src/com/android/launcher3/DeviceProfile.java index c1c12046e8..812cf9f8e4 100644 --- a/src/com/android/launcher3/DeviceProfile.java +++ b/src/com/android/launcher3/DeviceProfile.java @@ -207,11 +207,10 @@ public class DeviceProfile { // Add a bit of space between nav bar and hotseat in multi-window vertical bar layout. hotseatBarSidePaddingStartPx = isMultiWindowMode && isVerticalBarLayout() ? edgeMarginPx : 0; - hotseatBarSizePx = isVerticalBarLayout() - ? Utilities.pxFromDp(inv.iconSize, dm) + hotseatBarSidePaddingStartPx - + hotseatBarSidePaddingEndPx - : res.getDimensionPixelSize(R.dimen.dynamic_grid_hotseat_size) - + hotseatBarTopPaddingPx + hotseatBarBottomPaddingPx; + hotseatBarSizePx = Utilities.pxFromDp(inv.iconSize, dm) + (isVerticalBarLayout() + ? (hotseatBarSidePaddingStartPx + hotseatBarSidePaddingEndPx) + : (res.getDimensionPixelSize(R.dimen.dynamic_grid_hotseat_extra_vertical_size) + + hotseatBarTopPaddingPx + hotseatBarBottomPaddingPx)); // Calculate all of the remaining variables. updateAvailableDimensions(dm, res); diff --git a/src/com/android/launcher3/InvariantDeviceProfile.java b/src/com/android/launcher3/InvariantDeviceProfile.java index 0b2f4d921e..257e46c2f4 100644 --- a/src/com/android/launcher3/InvariantDeviceProfile.java +++ b/src/com/android/launcher3/InvariantDeviceProfile.java @@ -24,6 +24,8 @@ import android.content.res.Configuration; import android.content.res.TypedArray; import android.content.res.XmlResourceParser; import android.graphics.Point; +import android.text.TextUtils; +import android.util.AttributeSet; import android.util.DisplayMetrics; import android.util.Log; import android.util.Xml; @@ -32,15 +34,12 @@ import android.view.WindowManager; import com.android.launcher3.util.ConfigMonitor; import com.android.launcher3.util.MainThreadInitializedObject; -import com.android.launcher3.util.Thunk; import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; import java.io.IOException; import java.util.ArrayList; -import java.util.Collections; -import java.util.Comparator; import androidx.annotation.VisibleForTesting; @@ -50,6 +49,8 @@ public class InvariantDeviceProfile { public static final MainThreadInitializedObject INSTANCE = new MainThreadInitializedObject<>(InvariantDeviceProfile::new); + private static final String KEY_IDP_GRIP_NAME = "idp_grid_name"; + private static final float ICON_SIZE_DEFINED_IN_APP_DP = 48; public static final int CHANGE_FLAG_GRID = 1 << 0; @@ -63,11 +64,6 @@ public class InvariantDeviceProfile { // used to offset float not being able to express extremely small weights in extreme cases. private static float WEIGHT_EFFICIENT = 100000f; - // Profile-defining invariant properties - private String name; - private float minWidthDps; - private float minHeightDps; - /** * Number of icons per row and column in the workspace. */ @@ -90,7 +86,7 @@ public class InvariantDeviceProfile { */ public int numHotseatIcons; - int defaultLayoutId; + public int defaultLayoutId; int demoModeLayoutId; public DeviceProfile landscapeProfile; @@ -105,37 +101,26 @@ public class InvariantDeviceProfile { public InvariantDeviceProfile() {} private InvariantDeviceProfile(InvariantDeviceProfile p) { - this(p.name, p.minWidthDps, p.minHeightDps, p.numRows, p.numColumns, - p.numFolderRows, p.numFolderColumns, - p.iconSize, p.landscapeIconSize, p.iconTextSize, p.numHotseatIcons, - p.defaultLayoutId, p.demoModeLayoutId); - } - - private InvariantDeviceProfile(String n, float w, float h, int r, int c, int fr, int fc, - float is, float lis, float its, int hs, int dlId, int dmlId) { - name = n; - minWidthDps = w; - minHeightDps = h; - numRows = r; - numColumns = c; - numFolderRows = fr; - numFolderColumns = fc; - iconSize = is; - landscapeIconSize = lis; - iconTextSize = its; - numHotseatIcons = hs; - defaultLayoutId = dlId; - demoModeLayoutId = dmlId; + numRows = p.numRows; + numColumns = p.numColumns; + numFolderRows = p.numFolderRows; + numFolderColumns = p.numFolderColumns; + iconSize = p.iconSize; + landscapeIconSize = p.landscapeIconSize; + iconTextSize = p.iconTextSize; + numHotseatIcons = p.numHotseatIcons; + defaultLayoutId = p.defaultLayoutId; + demoModeLayoutId = p.demoModeLayoutId; } @TargetApi(23) private InvariantDeviceProfile(Context context) { - initGrid(context); + initGrid(context, Utilities.getPrefs(context).getString(KEY_IDP_GRIP_NAME, null)); mConfigMonitor = new ConfigMonitor(context, APPLY_CONFIG_AT_RUNTIME.get() ? this::onConfigChanged : this::killProcess); } - private void initGrid(Context context) { + private void initGrid(Context context, String gridName) { WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE); Display display = wm.getDefaultDisplay(); DisplayMetrics dm = new DisplayMetrics(); @@ -145,16 +130,18 @@ public class InvariantDeviceProfile { Point largestSize = new Point(); display.getCurrentSizeRange(smallestSize, largestSize); + ArrayList allOptions = getPredefinedDeviceProfiles(context, gridName); // This guarantees that width < height - minWidthDps = Utilities.dpiFromPx(Math.min(smallestSize.x, smallestSize.y), dm); - minHeightDps = Utilities.dpiFromPx(Math.min(largestSize.x, largestSize.y), dm); + float minWidthDps = Utilities.dpiFromPx(Math.min(smallestSize.x, smallestSize.y), dm); + float minHeightDps = Utilities.dpiFromPx(Math.min(largestSize.x, largestSize.y), dm); + // Sort the profiles based on the closeness to the device size + allOptions.sort((a, b) -> + Float.compare(dist(minWidthDps, minHeightDps, a.minWidthDps, a.minHeightDps), + dist(minWidthDps, minHeightDps, b.minWidthDps, b.minHeightDps))); + DisplayOption interpolatedDisplayOption = + invDistWeightedInterpolate(minWidthDps, minHeightDps, allOptions); - ArrayList closestProfiles = findClosestDeviceProfiles( - minWidthDps, minHeightDps, getPredefinedDeviceProfiles(context)); - InvariantDeviceProfile interpolatedDeviceProfileOut = - invDistWeightedInterpolate(minWidthDps, minHeightDps, closestProfiles); - - InvariantDeviceProfile closestProfile = closestProfiles.get(0); + GridOption closestProfile = allOptions.get(0).grid; numRows = closestProfile.numRows; numColumns = closestProfile.numColumns; numHotseatIcons = closestProfile.numHotseatIcons; @@ -162,11 +149,15 @@ public class InvariantDeviceProfile { demoModeLayoutId = closestProfile.demoModeLayoutId; numFolderRows = closestProfile.numFolderRows; numFolderColumns = closestProfile.numFolderColumns; + if (!closestProfile.name.equals(gridName)) { + Utilities.getPrefs(context).edit() + .putString(KEY_IDP_GRIP_NAME, closestProfile.name).apply(); + } - iconSize = interpolatedDeviceProfileOut.iconSize; - landscapeIconSize = interpolatedDeviceProfileOut.landscapeIconSize; + iconSize = interpolatedDisplayOption.iconSize; + landscapeIconSize = interpolatedDisplayOption.landscapeIconSize; iconBitmapSize = Utilities.pxFromDp(iconSize, dm); - iconTextSize = interpolatedDeviceProfileOut.iconTextSize; + iconTextSize = interpolatedDisplayOption.iconTextSize; fillResIconDpi = getLauncherIconDensity(iconBitmapSize); // If the partner customization apk contains any grid overrides, apply them @@ -210,7 +201,7 @@ public class InvariantDeviceProfile { InvariantDeviceProfile oldProfile = new InvariantDeviceProfile(this); // Re-init grid - initGrid(context); + initGrid(context, Utilities.getPrefs(context).getString(KEY_IDP_GRIP_NAME, null)); int changeFlags = 0; if (numRows != oldProfile.numRows || @@ -234,41 +225,53 @@ public class InvariantDeviceProfile { } } - ArrayList getPredefinedDeviceProfiles(Context context) { - ArrayList profiles = new ArrayList<>(); + static ArrayList getPredefinedDeviceProfiles(Context context, String gridName) { + ArrayList profiles = new ArrayList<>(); try (XmlResourceParser parser = context.getResources().getXml(R.xml.device_profiles)) { final int depth = parser.getDepth(); int type; while (((type = parser.next()) != XmlPullParser.END_TAG || parser.getDepth() > depth) && type != XmlPullParser.END_DOCUMENT) { - if ((type == XmlPullParser.START_TAG) && "profile".equals(parser.getName())) { - TypedArray a = context.obtainStyledAttributes( - Xml.asAttributeSet(parser), R.styleable.InvariantDeviceProfile); - int numRows = a.getInt(R.styleable.InvariantDeviceProfile_numRows, 0); - int numColumns = a.getInt(R.styleable.InvariantDeviceProfile_numColumns, 0); - float iconSize = a.getFloat(R.styleable.InvariantDeviceProfile_iconSize, 0); - profiles.add(new InvariantDeviceProfile( - a.getString(R.styleable.InvariantDeviceProfile_name), - a.getFloat(R.styleable.InvariantDeviceProfile_minWidthDps, 0), - a.getFloat(R.styleable.InvariantDeviceProfile_minHeightDps, 0), - numRows, - numColumns, - a.getInt(R.styleable.InvariantDeviceProfile_numFolderRows, numRows), - a.getInt(R.styleable.InvariantDeviceProfile_numFolderColumns, numColumns), - iconSize, - a.getFloat(R.styleable.InvariantDeviceProfile_landscapeIconSize, iconSize), - a.getFloat(R.styleable.InvariantDeviceProfile_iconTextSize, 0), - a.getInt(R.styleable.InvariantDeviceProfile_numHotseatIcons, numColumns), - a.getResourceId(R.styleable.InvariantDeviceProfile_defaultLayoutId, 0), - a.getResourceId(R.styleable.InvariantDeviceProfile_demoModeLayoutId, 0))); - a.recycle(); + if ((type == XmlPullParser.START_TAG) && "grid-option".equals(parser.getName())) { + + GridOption gridOption = new GridOption(context, Xml.asAttributeSet(parser)); + final int displayDepth = parser.getDepth(); + while (((type = parser.next()) != XmlPullParser.END_TAG || + parser.getDepth() > displayDepth) + && type != XmlPullParser.END_DOCUMENT) { + if ((type == XmlPullParser.START_TAG) && "display-option".equals( + parser.getName())) { + profiles.add(new DisplayOption( + gridOption, context, Xml.asAttributeSet(parser))); + } + } } } } catch (IOException|XmlPullParserException e) { throw new RuntimeException(e); } - return profiles; + + ArrayList filteredProfiles = new ArrayList<>(); + if (!TextUtils.isEmpty(gridName)) { + for (DisplayOption option : profiles) { + if (gridName.equals(option.grid.name)) { + filteredProfiles.add(option); + } + } + } + if (filteredProfiles.isEmpty()) { + // No grid found, use the default options + for (DisplayOption option : profiles) { + if (option.canBeDefault) { + filteredProfiles.add(option); + } + } + } + if (filteredProfiles.isEmpty()) { + throw new RuntimeException("No display option with canBeDefault=true"); + } + return filteredProfiles; } private int getLauncherIconDensity(int requiredSize) { @@ -307,60 +310,40 @@ public class InvariantDeviceProfile { } } - @Thunk float dist(float x0, float y0, float x1, float y1) { + private static float dist(float x0, float y0, float x1, float y1) { return (float) Math.hypot(x1 - x0, y1 - y0); } /** * Returns the closest device profiles ordered by closeness to the specified width and height */ - // Package private visibility for testing. - ArrayList findClosestDeviceProfiles( - final float width, final float height, ArrayList points) { - - // Sort the profiles by their closeness to the dimensions - ArrayList pointsByNearness = points; - Collections.sort(pointsByNearness, new Comparator() { - public int compare(InvariantDeviceProfile a, InvariantDeviceProfile b) { - return Float.compare(dist(width, height, a.minWidthDps, a.minHeightDps), - dist(width, height, b.minWidthDps, b.minHeightDps)); - } - }); - - return pointsByNearness; + @VisibleForTesting + static ArrayList sortByClosenessToSize( + float width, float height, ArrayList points) { + points.sort((a, b) -> + Float.compare(dist(width, height, a.minWidthDps, a.minHeightDps), + dist(width, height, b.minWidthDps, b.minHeightDps))); + return points; } - // Package private visibility for testing. - InvariantDeviceProfile invDistWeightedInterpolate(float width, float height, - ArrayList points) { + @VisibleForTesting + static DisplayOption invDistWeightedInterpolate(float width, float height, + ArrayList points) { float weights = 0; - InvariantDeviceProfile p = points.get(0); + DisplayOption p = points.get(0); if (dist(width, height, p.minWidthDps, p.minHeightDps) == 0) { return p; } - InvariantDeviceProfile out = new InvariantDeviceProfile(); + DisplayOption out = new DisplayOption(); for (int i = 0; i < points.size() && i < KNEARESTNEIGHBOR; ++i) { - p = new InvariantDeviceProfile(points.get(i)); + p = points.get(i); float w = weight(width, height, p.minWidthDps, p.minHeightDps, WEIGHT_POWER); weights += w; - out.add(p.multiply(w)); + out.add(new DisplayOption().add(p).multiply(w)); } - return out.multiply(1.0f/weights); - } - - private void add(InvariantDeviceProfile p) { - iconSize += p.iconSize; - landscapeIconSize += p.landscapeIconSize; - iconTextSize += p.iconTextSize; - } - - private InvariantDeviceProfile multiply(float w) { - iconSize *= w; - landscapeIconSize *= w; - iconTextSize *= w; - return this; + return out.multiply(1.0f / weights); } public DeviceProfile getDeviceProfile(Context context) { @@ -368,7 +351,7 @@ public class InvariantDeviceProfile { == Configuration.ORIENTATION_LANDSCAPE ? landscapeProfile : portraitProfile; } - private float weight(float x0, float y0, float x1, float y1, float pow) { + private static float weight(float x0, float y0, float x1, float y1, float pow) { float d = dist(x0, y0, x1, y1); if (Float.compare(d, 0f) == 0) { return Float.POSITIVE_INFINITY; @@ -409,4 +392,94 @@ public class InvariantDeviceProfile { void onIdpChanged(int changeFlags, InvariantDeviceProfile profile); } + + + private static final class GridOption { + + private final String name; + private final int numRows; + private final int numColumns; + + private final int numFolderRows; + private final int numFolderColumns; + + private final int numHotseatIcons; + + private final int defaultLayoutId; + private final int demoModeLayoutId; + + GridOption(Context context, AttributeSet attrs) { + TypedArray a = context.obtainStyledAttributes( + attrs, R.styleable.GridDisplayOption); + name = a.getString(R.styleable.GridDisplayOption_name); + numRows = a.getInt(R.styleable.GridDisplayOption_numRows, 0); + numColumns = a.getInt(R.styleable.GridDisplayOption_numColumns, 0); + + defaultLayoutId = a.getResourceId( + R.styleable.GridDisplayOption_defaultLayoutId, 0); + demoModeLayoutId = a.getResourceId( + R.styleable.GridDisplayOption_demoModeLayoutId, defaultLayoutId); + numHotseatIcons = a.getInt( + R.styleable.GridDisplayOption_numHotseatIcons, numColumns); + numFolderRows = a.getInt( + R.styleable.GridDisplayOption_numFolderRows, numRows); + numFolderColumns = a.getInt( + R.styleable.GridDisplayOption_numFolderColumns, numColumns); + a.recycle(); + } + } + + private static final class DisplayOption { + private final GridOption grid; + + private final String name; + private final float minWidthDps; + private final float minHeightDps; + private final boolean canBeDefault; + + private float iconSize; + private float landscapeIconSize; + private float iconTextSize; + + DisplayOption(GridOption grid, Context context, AttributeSet attrs) { + this.grid = grid; + + TypedArray a = context.obtainStyledAttributes( + attrs, R.styleable.ProfileDisplayOption); + + name = a.getString(R.styleable.ProfileDisplayOption_name); + minWidthDps = a.getFloat(R.styleable.ProfileDisplayOption_minWidthDps, 0); + minHeightDps = a.getFloat(R.styleable.ProfileDisplayOption_minHeightDps, 0); + canBeDefault = a.getBoolean( + R.styleable.ProfileDisplayOption_canBeDefault, false); + + iconSize = a.getFloat(R.styleable.ProfileDisplayOption_iconSize, 0); + landscapeIconSize = a.getFloat(R.styleable.ProfileDisplayOption_landscapeIconSize, + iconSize); + iconTextSize = a.getFloat(R.styleable.ProfileDisplayOption_iconTextSize, 0); + a.recycle(); + } + + DisplayOption() { + grid = null; + name = null; + minWidthDps = 0; + minHeightDps = 0; + canBeDefault = false; + } + + private DisplayOption multiply(float w) { + iconSize *= w; + landscapeIconSize *= w; + iconTextSize *= w; + return this; + } + + private DisplayOption add(DisplayOption p) { + iconSize += p.iconSize; + landscapeIconSize += p.landscapeIconSize; + iconTextSize += p.iconTextSize; + return this; + } + } } \ No newline at end of file diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java index b4cc1ceffc..9125a98433 100644 --- a/src/com/android/launcher3/Launcher.java +++ b/src/com/android/launcher3/Launcher.java @@ -19,6 +19,7 @@ package com.android.launcher3; import static android.content.pm.ActivityInfo.CONFIG_LOCALE; import static android.content.pm.ActivityInfo.CONFIG_ORIENTATION; import static android.content.pm.ActivityInfo.CONFIG_SCREEN_SIZE; + import static com.android.launcher3.AbstractFloatingView.TYPE_SNACKBAR; import static com.android.launcher3.LauncherAnimUtils.SPRING_LOADED_EXIT_DELAY; import static com.android.launcher3.LauncherState.ALL_APPS; @@ -26,6 +27,8 @@ import static com.android.launcher3.LauncherState.NORMAL; import static com.android.launcher3.dragndrop.DragLayer.ALPHA_INDEX_LAUNCHER_LOAD; import static com.android.launcher3.logging.LoggerUtils.newContainerTarget; import static com.android.launcher3.logging.LoggerUtils.newTarget; +import static com.android.launcher3.util.RaceConditionTracker.ENTER; +import static com.android.launcher3.util.RaceConditionTracker.EXIT; import android.animation.Animator; import android.animation.AnimatorListenerAdapter; @@ -71,16 +74,18 @@ import android.view.accessibility.AccessibilityEvent; import android.view.animation.OvershootInterpolator; import android.widget.Toast; +import androidx.annotation.Nullable; + import com.android.launcher3.DropTarget.DragObject; import com.android.launcher3.accessibility.LauncherAccessibilityDelegate; import com.android.launcher3.allapps.AllAppsContainerView; import com.android.launcher3.allapps.AllAppsTransitionController; import com.android.launcher3.allapps.DiscoveryBounce; import com.android.launcher3.anim.PropertyListBuilder; -import com.android.launcher3.dot.DotInfo; import com.android.launcher3.compat.AppWidgetManagerCompat; import com.android.launcher3.compat.LauncherAppsCompatVO; import com.android.launcher3.config.FeatureFlags; +import com.android.launcher3.dot.DotInfo; import com.android.launcher3.dragndrop.DragController; import com.android.launcher3.dragndrop.DragLayer; import com.android.launcher3.dragndrop.DragView; @@ -116,6 +121,7 @@ import com.android.launcher3.util.MultiValueAlpha.AlphaProperty; import com.android.launcher3.util.PackageManagerHelper; import com.android.launcher3.util.PackageUserKey; import com.android.launcher3.util.PendingRequestArgs; +import com.android.launcher3.util.RaceConditionTracker; import com.android.launcher3.util.SystemUiController; import com.android.launcher3.util.Themes; import com.android.launcher3.util.Thunk; @@ -143,8 +149,6 @@ import java.util.HashSet; import java.util.List; import java.util.Set; -import androidx.annotation.Nullable; - /** * Default launcher application. */ @@ -184,6 +188,7 @@ public class Launcher extends BaseDraggingActivity implements LauncherExterns, private static final String RUNTIME_STATE_PENDING_ACTIVITY_RESULT = "launcher.activity_result"; // Type: SparseArray private static final String RUNTIME_STATE_WIDGET_PANEL = "launcher.widget_panel"; + public static final String ON_CREATE_EVT = "Launcher.onCreate"; private LauncherStateManager mStateManager; @@ -255,6 +260,7 @@ public class Launcher extends BaseDraggingActivity implements LauncherExterns, @Override protected void onCreate(Bundle savedInstanceState) { + RaceConditionTracker.onEvent(ON_CREATE_EVT, ENTER); if (DEBUG_STRICT_MODE) { StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder() .detectDiskReads() @@ -349,6 +355,7 @@ public class Launcher extends BaseDraggingActivity implements LauncherExterns, mRotationHelper.initialize(); TraceHelper.endSection("Launcher-onCreate"); + RaceConditionTracker.onEvent(ON_CREATE_EVT, EXIT); } @Override @@ -1747,7 +1754,6 @@ public class Launcher extends BaseDraggingActivity implements LauncherExterns, orderedScreenIds.indexOf(Workspace.FIRST_SCREEN_ID) != 0) { orderedScreenIds.removeValue(Workspace.FIRST_SCREEN_ID); orderedScreenIds.add(0, Workspace.FIRST_SCREEN_ID); - LauncherModel.updateWorkspaceScreenOrder(this, orderedScreenIds); } else if (!FeatureFlags.QSB_ON_FIRST_SCREEN.get() && orderedScreenIds.isEmpty()) { // If there are no screens, we need to have an empty screen diff --git a/src/com/android/launcher3/LauncherModel.java b/src/com/android/launcher3/LauncherModel.java index a5f97de7ab..9b4c5fd095 100644 --- a/src/com/android/launcher3/LauncherModel.java +++ b/src/com/android/launcher3/LauncherModel.java @@ -20,12 +20,8 @@ import static com.android.launcher3.LauncherAppState.ACTION_FORCE_ROLOAD; import static com.android.launcher3.config.FeatureFlags.IS_DOGFOOD_BUILD; import android.content.BroadcastReceiver; -import android.content.ContentProviderOperation; -import android.content.ContentResolver; -import android.content.ContentValues; import android.content.Context; import android.content.Intent; -import android.net.Uri; import android.os.Handler; import android.os.HandlerThread; import android.os.Looper; @@ -38,8 +34,8 @@ import android.util.Pair; import com.android.launcher3.compat.LauncherAppsCompat; import com.android.launcher3.compat.PackageInstallerCompat.PackageInstallInfo; import com.android.launcher3.compat.UserManagerCompat; -import com.android.launcher3.icons.LauncherIcons; import com.android.launcher3.icons.IconCache; +import com.android.launcher3.icons.LauncherIcons; import com.android.launcher3.model.AddWorkspaceItemsTask; import com.android.launcher3.model.BaseModelUpdateTask; import com.android.launcher3.model.BgDataModel; @@ -51,7 +47,6 @@ import com.android.launcher3.model.PackageInstallStateChangedTask; import com.android.launcher3.model.PackageUpdatedTask; import com.android.launcher3.model.ShortcutsChangedTask; import com.android.launcher3.model.UserLockStateChangedTask; -import com.android.launcher3.provider.LauncherDbUtils; import com.android.launcher3.shortcuts.DeepShortcutManager; import com.android.launcher3.shortcuts.ShortcutInfoCompat; import com.android.launcher3.util.ComponentKey; @@ -70,7 +65,6 @@ import java.lang.ref.WeakReference; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; -import java.util.Iterator; import java.util.List; import java.util.concurrent.CancellationException; import java.util.concurrent.Executor; @@ -268,53 +262,6 @@ public class LauncherModel extends BroadcastReceiver runOnWorkerThread(r); } - /** - * Update the order of the workspace screens in the database. The array list contains - * a list of screen ids in the order that they should appear. - */ - public static void updateWorkspaceScreenOrder(Context context, IntArray screens) { - final ContentResolver cr = context.getContentResolver(); - final Uri uri = LauncherSettings.WorkspaceScreens.CONTENT_URI; - - // Create a copy with only non-negative values - final IntArray screensCopy = new IntArray(); - for (int i = 0; i < screens.size(); i++) { - int id = screens.get(i); - if (id >= 0) { - screensCopy.add(id); - } - } - - Runnable r = new Runnable() { - @Override - public void run() { - ArrayList ops = new ArrayList(); - // Clear the table - ops.add(ContentProviderOperation.newDelete(uri).build()); - int count = screensCopy.size(); - for (int i = 0; i < count; i++) { - ContentValues v = new ContentValues(); - int screenId = screensCopy.get(i); - v.put(LauncherSettings.WorkspaceScreens._ID, screenId); - v.put(LauncherSettings.WorkspaceScreens.SCREEN_RANK, i); - ops.add(ContentProviderOperation.newInsert(uri).withValues(v).build()); - } - - try { - cr.applyBatch(LauncherProvider.AUTHORITY, ops); - } catch (Exception ex) { - throw new RuntimeException(ex); - } - - synchronized (sBgDataModel) { - sBgDataModel.workspaceScreens.clear(); - sBgDataModel.workspaceScreens.addAll(screensCopy); - } - } - }; - runOnWorkerThread(r); - } - /** * Set this as the current Launcher activity object for the loader. */ @@ -519,18 +466,6 @@ public class LauncherModel extends BroadcastReceiver } } - /** - * Loads the workspace screen ids in an ordered list. - */ - public static IntArray loadWorkspaceScreensDb(Context context) { - final ContentResolver contentResolver = context.getContentResolver(); - final Uri screensUri = LauncherSettings.WorkspaceScreens.CONTENT_URI; - - // Get screens ordered by rank. - return LauncherDbUtils.getScreenIdsFromCursor(contentResolver.query( - screensUri, null, null, null, LauncherSettings.WorkspaceScreens.SCREEN_RANK)); - } - public void onInstallSessionCreated(final PackageInstallInfo sessionInfo) { enqueueModelUpdateTask(new BaseModelUpdateTask() { @Override diff --git a/src/com/android/launcher3/LauncherProvider.java b/src/com/android/launcher3/LauncherProvider.java index 7db3d5b9db..8ed3314efd 100644 --- a/src/com/android/launcher3/LauncherProvider.java +++ b/src/com/android/launcher3/LauncherProvider.java @@ -32,6 +32,7 @@ import android.content.SharedPreferences; import android.content.pm.PackageManager.NameNotFoundException; import android.content.res.Resources; import android.database.Cursor; +import android.database.DatabaseUtils; import android.database.SQLException; import android.database.sqlite.SQLiteDatabase; import android.database.sqlite.SQLiteQueryBuilder; @@ -45,12 +46,12 @@ import android.os.Message; import android.os.Process; import android.os.UserHandle; import android.os.UserManager; +import android.provider.BaseColumns; import android.text.TextUtils; import android.util.Log; import com.android.launcher3.AutoInstallsLayout.LayoutParserCallback; import com.android.launcher3.LauncherSettings.Favorites; -import com.android.launcher3.LauncherSettings.WorkspaceScreens; import com.android.launcher3.compat.UserManagerCompat; import com.android.launcher3.config.FeatureFlags; import com.android.launcher3.logging.FileLog; @@ -70,6 +71,7 @@ import java.io.PrintWriter; import java.net.URISyntaxException; import java.util.ArrayList; import java.util.Arrays; +import java.util.Locale; public class LauncherProvider extends ContentProvider { private static final String TAG = "LauncherProvider"; @@ -79,8 +81,9 @@ public class LauncherProvider extends ContentProvider { /** * Represents the schema of the database. Changes in scheme need not be backwards compatible. + * When increasing the scheme version, ensure that downgrade_schema.json is updated */ - public static final int SCHEMA_VERSION = 27; + public static final int SCHEMA_VERSION = 28; public static final String AUTHORITY = BuildConfig.APPLICATION_ID + ".settings"; @@ -175,10 +178,10 @@ public class LauncherProvider extends ContentProvider { if (values == null) { throw new RuntimeException("Error: attempting to insert null values"); } - if (!values.containsKey(LauncherSettings.ChangeLogColumns._ID)) { + if (!values.containsKey(LauncherSettings.Favorites._ID)) { throw new RuntimeException("Error: attempting to add item without specifying an id"); } - helper.checkId(table, values); + helper.checkId(values); return (int) db.insert(table, nullColumnHack, values); } @@ -262,24 +265,7 @@ public class LauncherProvider extends ContentProvider { } } - // Add screen id if not present - int screenId = values.getAsInteger(LauncherSettings.Favorites.SCREEN); - SQLiteStatement stmp = null; - try { - stmp = mOpenHelper.getWritableDatabase().compileStatement( - "INSERT OR IGNORE INTO workspaceScreens (_id, screenRank) " + - "select ?, (ifnull(MAX(screenRank), -1)+1) from workspaceScreens"); - stmp.bindLong(1, screenId); - - ContentValues valuesInserted = new ContentValues(); - valuesInserted.put(LauncherSettings.BaseLauncherColumns._ID, stmp.executeInsert()); - mOpenHelper.checkId(WorkspaceScreens.TABLE_NAME, valuesInserted); - return true; - } catch (Exception e) { - return false; - } finally { - Utilities.closeSilently(stmp); - } + return true; } @Override @@ -404,7 +390,6 @@ public class LauncherProvider extends ContentProvider { * @return Ids of deleted folders. */ private IntArray deleteEmptyFolders() { - IntArray folderIds = new IntArray(); SQLiteDatabase db = mOpenHelper.getWritableDatabase(); try (SQLiteTransaction t = new SQLiteTransaction(db)) { // Select folders whose id do not match any container value. @@ -413,21 +398,19 @@ public class LauncherProvider extends ContentProvider { + LauncherSettings.Favorites._ID + " NOT IN (SELECT " + LauncherSettings.Favorites.CONTAINER + " FROM " + Favorites.TABLE_NAME + ")"; - try (Cursor c = db.query(Favorites.TABLE_NAME, - new String[] {LauncherSettings.Favorites._ID}, - selection, null, null, null, null)) { - LauncherDbUtils.iterateCursor(c, 0, folderIds); - } + + IntArray folderIds = LauncherDbUtils.queryIntArray(db, Favorites.TABLE_NAME, + Favorites._ID, selection, null, null); if (!folderIds.isEmpty()) { db.delete(Favorites.TABLE_NAME, Utilities.createDbSelectionQuery( LauncherSettings.Favorites._ID, folderIds), null); } t.commit(); + return folderIds; } catch (SQLException ex) { Log.e(TAG, ex.getMessage(), ex); - folderIds.clear(); + return new IntArray(); } - return folderIds; } /** @@ -438,7 +421,7 @@ public class LauncherProvider extends ContentProvider { } @Thunk static void addModifiedTime(ContentValues values) { - values.put(LauncherSettings.ChangeLogColumns.MODIFIED, System.currentTimeMillis()); + values.put(LauncherSettings.Favorites.MODIFIED, System.currentTimeMillis()); } private void clearFlagEmptyDbCreated() { @@ -551,11 +534,10 @@ public class LauncherProvider extends ContentProvider { // Table creation sometimes fails silently, which leads to a crash loop. // This way, we will try to create a table every time after crash, so the device // would eventually be able to recover. - if (!tableExists(Favorites.TABLE_NAME) || !tableExists(WorkspaceScreens.TABLE_NAME)) { + if (!tableExists(Favorites.TABLE_NAME)) { Log.e(TAG, "Tables are missing after onCreate has been called. Trying to recreate"); // This operation is a no-op if the table already exists. addFavoritesTable(getWritableDatabase(), true); - addWorkspacesTable(getWritableDatabase(), true); } initIds(); @@ -602,7 +584,6 @@ public class LauncherProvider extends ContentProvider { mMaxScreenId = 0; addFavoritesTable(db, false); - addWorkspacesTable(db, false); // Fresh and clean launcher DB. mMaxItemId = initializeMaxItemId(db); @@ -633,46 +614,6 @@ public class LauncherProvider extends ContentProvider { Favorites.addTableToDb(db, getDefaultUserSerial(), optional); } - private void addWorkspacesTable(SQLiteDatabase db, boolean optional) { - String ifNotExists = optional ? " IF NOT EXISTS " : ""; - db.execSQL("CREATE TABLE " + ifNotExists + WorkspaceScreens.TABLE_NAME + " (" + - LauncherSettings.WorkspaceScreens._ID + " INTEGER PRIMARY KEY," + - LauncherSettings.WorkspaceScreens.SCREEN_RANK + " INTEGER," + - LauncherSettings.ChangeLogColumns.MODIFIED + " INTEGER NOT NULL DEFAULT 0" + - ");"); - } - - private void removeOrphanedItems(SQLiteDatabase db) { - // Delete items directly on the workspace who's screen id doesn't exist - // "DELETE FROM favorites WHERE screen NOT IN (SELECT _id FROM workspaceScreens) - // AND container = -100" - String removeOrphanedDesktopItems = "DELETE FROM " + Favorites.TABLE_NAME + - " WHERE " + - LauncherSettings.Favorites.SCREEN + " NOT IN (SELECT " + - LauncherSettings.WorkspaceScreens._ID + " FROM " + WorkspaceScreens.TABLE_NAME + ")" + - " AND " + - LauncherSettings.Favorites.CONTAINER + " = " + - LauncherSettings.Favorites.CONTAINER_DESKTOP; - db.execSQL(removeOrphanedDesktopItems); - - // Delete items contained in folders which no longer exist (after above statement) - // "DELETE FROM favorites WHERE container <> -100 AND container <> -101 AND container - // NOT IN (SELECT _id FROM favorites WHERE itemType = 2)" - String removeOrphanedFolderItems = "DELETE FROM " + Favorites.TABLE_NAME + - " WHERE " + - LauncherSettings.Favorites.CONTAINER + " <> " + - LauncherSettings.Favorites.CONTAINER_DESKTOP + - " AND " - + LauncherSettings.Favorites.CONTAINER + " <> " + - LauncherSettings.Favorites.CONTAINER_HOTSEAT + - " AND " - + LauncherSettings.Favorites.CONTAINER + " NOT IN (SELECT " + - LauncherSettings.Favorites._ID + " FROM " + Favorites.TABLE_NAME + - " WHERE " + LauncherSettings.Favorites.ITEM_TYPE + " = " + - LauncherSettings.Favorites.ITEM_TYPE_FOLDER + ")"; - db.execSQL(removeOrphanedFolderItems); - } - @Override public void onOpen(SQLiteDatabase db) { super.onOpen(db); @@ -681,8 +622,7 @@ public class LauncherProvider extends ContentProvider { if (!schemaFile.exists()) { handleOneTimeDataUpgrade(db); } - DbDowngradeHelper.updateSchemaFile(schemaFile, SCHEMA_VERSION, mContext, - R.raw.downgrade_schema); + DbDowngradeHelper.updateSchemaFile(schemaFile, SCHEMA_VERSION, mContext); } /** @@ -709,12 +649,8 @@ public class LauncherProvider extends ContentProvider { switch (oldVersion) { // The version cannot be lower that 12, as Launcher3 never supported a lower // version of the DB. - case 12: { - // With the new shrink-wrapped and re-orderable workspaces, it makes sense - // to persist workspace screens and their relative order. - mMaxScreenId = 0; - addWorkspacesTable(db, false); - } + case 12: + // No-op case 13: { try (SQLiteTransaction t = new SQLiteTransaction(db)) { // Insert new column for holding widget provider name @@ -728,15 +664,7 @@ public class LauncherProvider extends ContentProvider { } } case 14: { - try (SQLiteTransaction t = new SQLiteTransaction(db)) { - // Insert new column for holding update timestamp - db.execSQL("ALTER TABLE favorites " + - "ADD COLUMN modified INTEGER NOT NULL DEFAULT 0;"); - db.execSQL("ALTER TABLE workspaceScreens " + - "ADD COLUMN modified INTEGER NOT NULL DEFAULT 0;"); - t.commit(); - } catch (SQLException ex) { - Log.e(TAG, ex.getMessage(), ex); + if (!addIntegerColumn(db, Favorites.MODIFIED, 0)) { // Old version remains, which means we wipe old data break; } @@ -747,22 +675,15 @@ public class LauncherProvider extends ContentProvider { break; } } - case 16: { + case 16: // No-op - } - case 17: { + case 17: + // No-op + case 18: // No-op - } - case 18: { - // Due to a data loss bug, some users may have items associated with screen ids - // which no longer exist. Since this can cause other problems, and since the user - // will never see these items anyway, we use database upgrade as an opportunity to - // clean things up. - removeOrphanedItems(db); - } case 19: { // Add userId column - if (!addProfileColumn(db)) { + if (!addIntegerColumn(db, Favorites.PROFILE_ID, getDefaultUserSerial())) { // Old version remains, which means we wipe old data break; } @@ -772,10 +693,7 @@ public class LauncherProvider extends ContentProvider { break; } case 21: - // Recreate workspace table with screen id a primary key - if (!recreateWorkspaceTable(db)) { - break; - } + // No-op case 22: { if (!addIntegerColumn(db, Favorites.OPTIONS, 0)) { // Old version remains, which means we wipe old data @@ -794,7 +712,30 @@ public class LauncherProvider extends ContentProvider { !LauncherDbUtils.prepareScreenZeroToHostQsb(mContext, db)) { break; } - case 27: + case 27: { + // Update the favorites table so that the screen ids are ordered based on + // workspace page rank. + IntArray finalScreens = LauncherDbUtils.queryIntArray(db, "workspaceScreens", + BaseColumns._ID, null, null, "screenRank"); + int[] original = finalScreens.toArray(); + Arrays.sort(original); + String updatemap = ""; + for (int i = 0; i < original.length; i++) { + if (finalScreens.get(i) != original[i]) { + updatemap += String.format(Locale.ENGLISH, " WHEN %1$s=%2$d THEN %3$d", + Favorites.SCREEN, finalScreens.get(i), original[i]); + } + } + if (!TextUtils.isEmpty(updatemap)) { + String query = String.format(Locale.ENGLISH, + "UPDATE %1$s SET %2$s=CASE %3$s ELSE %2$s END WHERE %4$s = %5$d", + Favorites.TABLE_NAME, Favorites.SCREEN, updatemap, + Favorites.CONTAINER, Favorites.CONTAINER_DESKTOP); + db.execSQL(query); + } + db.execSQL("DROP TABLE IF EXISTS workspaceScreens"); + } + case 28: // DB Upgraded successfully return; } @@ -822,7 +763,7 @@ public class LauncherProvider extends ContentProvider { public void createEmptyDB(SQLiteDatabase db) { try (SQLiteTransaction t = new SQLiteTransaction(db)) { db.execSQL("DROP TABLE IF EXISTS " + Favorites.TABLE_NAME); - db.execSQL("DROP TABLE IF EXISTS " + WorkspaceScreens.TABLE_NAME); + db.execSQL("DROP TABLE IF EXISTS workspaceScreens"); onCreate(db); t.commit(); } @@ -845,17 +786,9 @@ public class LauncherProvider extends ContentProvider { Log.e(TAG, "getAppWidgetIds not supported", e); return; } - final IntSet validWidgets = new IntSet(); - try (Cursor c = db.query(Favorites.TABLE_NAME, - new String[] {Favorites.APPWIDGET_ID }, - "itemType=" + Favorites.ITEM_TYPE_APPWIDGET, null, null, null, null)) { - while (c.moveToNext()) { - validWidgets.add(c.getInt(0)); - } - } catch (SQLException ex) { - Log.w(TAG, "Error getting widgets list", ex); - return; - } + final IntSet validWidgets = IntSet.wrap(LauncherDbUtils.queryIntArray(db, + Favorites.TABLE_NAME, Favorites.APPWIDGET_ID, + "itemType=" + Favorites.ITEM_TYPE_APPWIDGET, null, null)); for (int widgetId : allWidgets) { if (!validWidgets.contains(widgetId)) { try { @@ -910,46 +843,6 @@ public class LauncherProvider extends ContentProvider { } } - /** - * Recreates workspace table and migrates data to the new table. - */ - public boolean recreateWorkspaceTable(SQLiteDatabase db) { - try (SQLiteTransaction t = new SQLiteTransaction(db)) { - final IntArray sortedIDs; - - try (Cursor c = db.query(WorkspaceScreens.TABLE_NAME, - new String[] {LauncherSettings.WorkspaceScreens._ID}, - null, null, null, null, - LauncherSettings.WorkspaceScreens.SCREEN_RANK)) { - // Use LinkedHashSet so that ordering is preserved - sortedIDs = LauncherDbUtils.getScreenIdsFromCursor(c); - } - db.execSQL("DROP TABLE IF EXISTS " + WorkspaceScreens.TABLE_NAME); - addWorkspacesTable(db, false); - - // Add all screen ids back - int total = sortedIDs.size(); - for (int i = 0; i < total; i++) { - ContentValues values = new ContentValues(); - values.put(LauncherSettings.WorkspaceScreens._ID, sortedIDs.get(i)); - values.put(LauncherSettings.WorkspaceScreens.SCREEN_RANK, i); - addModifiedTime(values); - db.insertOrThrow(WorkspaceScreens.TABLE_NAME, null, values); - } - t.commit(); - - mMaxScreenId = 0; - for (int i = 0; i < sortedIDs.size(); i++) { - mMaxScreenId = Math.max(mMaxScreenId, sortedIDs.get(i)); - } - } catch (SQLException ex) { - // Old version remains, which means we wipe old data - Log.e(TAG, ex.getMessage(), ex); - return false; - } - return true; - } - @Thunk boolean updateFolderItemsRank(SQLiteDatabase db, boolean addRankColumn) { try (SQLiteTransaction t = new SQLiteTransaction(db)) { if (addRankColumn) { @@ -979,10 +872,6 @@ public class LauncherProvider extends ContentProvider { return true; } - private boolean addProfileColumn(SQLiteDatabase db) { - return addIntegerColumn(db, Favorites.PROFILE_ID, getDefaultUserSerial()); - } - private boolean addIntegerColumn(SQLiteDatabase db, String columnName, long defaultValue) { try (SQLiteTransaction t = new SQLiteTransaction(db)) { db.execSQL("ALTER TABLE favorites ADD COLUMN " @@ -1018,17 +907,20 @@ public class LauncherProvider extends ContentProvider { return dbInsertAndCheck(this, db, Favorites.TABLE_NAME, null, values); } - public void checkId(String table, ContentValues values) { - int id = values.getAsInteger(LauncherSettings.BaseLauncherColumns._ID); - if (WorkspaceScreens.TABLE_NAME.equals(table)) { - mMaxScreenId = Math.max(id, mMaxScreenId); - } else { - mMaxItemId = Math.max(id, mMaxItemId); + public void checkId(ContentValues values) { + int id = values.getAsInteger(Favorites._ID); + mMaxItemId = Math.max(id, mMaxItemId); + + Integer screen = values.getAsInteger(Favorites.SCREEN); + Integer container = values.getAsInteger(Favorites.CONTAINER); + if (screen != null && container != null + && container.intValue() == Favorites.CONTAINER_DESKTOP) { + mMaxScreenId = Math.max(screen, mMaxScreenId); } } private int initializeMaxItemId(SQLiteDatabase db) { - return getMaxId(db, Favorites.TABLE_NAME); + return getMaxId(db, "SELECT MAX(%1$s) FROM %2$s", Favorites._ID, Favorites.TABLE_NAME); } // Generates a new ID to use for an workspace screen in your database. This method @@ -1045,34 +937,18 @@ public class LauncherProvider extends ContentProvider { } private int initializeMaxScreenId(SQLiteDatabase db) { - return getMaxId(db, WorkspaceScreens.TABLE_NAME); + return getMaxId(db, "SELECT MAX(%1$s) FROM %2$s WHERE %3$s = %4$d", + Favorites.SCREEN, Favorites.TABLE_NAME, Favorites.CONTAINER, + Favorites.CONTAINER_DESKTOP); } @Thunk int loadFavorites(SQLiteDatabase db, AutoInstallsLayout loader) { - IntArray screenIds = new IntArray(); // TODO: Use multiple loaders with fall-back and transaction. - int count = loader.loadLayout(db, screenIds); - - // Add the screens specified by the items above - int[] sortedScreenIds = screenIds.toArray(); - Arrays.sort(sortedScreenIds); - int rank = 0; - ContentValues values = new ContentValues(); - for (int id : sortedScreenIds) { - values.clear(); - values.put(LauncherSettings.WorkspaceScreens._ID, id); - values.put(LauncherSettings.WorkspaceScreens.SCREEN_RANK, rank); - if (dbInsertAndCheck(this, db, WorkspaceScreens.TABLE_NAME, null, values) < 0) { - throw new RuntimeException("Failed initialize screen table" - + "from default layout"); - } - rank++; - } + int count = loader.loadLayout(db, new IntArray()); // Ensure that the max ids are initialized mMaxItemId = initializeMaxItemId(db); mMaxScreenId = initializeMaxScreenId(db); - return count; } } @@ -1080,22 +956,14 @@ public class LauncherProvider extends ContentProvider { /** * @return the max _id in the provided table. */ - @Thunk static int getMaxId(SQLiteDatabase db, String table) { - Cursor c = db.rawQuery("SELECT MAX(_id) FROM " + table, null); - // get the result - int id = -1; - if (c != null && c.moveToNext()) { - id = c.getInt(0); + @Thunk static int getMaxId(SQLiteDatabase db, String query, Object... args) { + int max = (int) DatabaseUtils.longForQuery(db, + String.format(Locale.ENGLISH, query, args), + null); + if (max < 0) { + throw new RuntimeException("Error: could not query max id"); } - if (c != null) { - c.close(); - } - - if (id == -1) { - throw new RuntimeException("Error: could not query max id in " + table); - } - - return id; + return max; } static class SqlArguments { diff --git a/src/com/android/launcher3/LauncherSettings.java b/src/com/android/launcher3/LauncherSettings.java index 0b12b15f9e..79c8208736 100644 --- a/src/com/android/launcher3/LauncherSettings.java +++ b/src/com/android/launcher3/LauncherSettings.java @@ -26,16 +26,17 @@ import android.provider.BaseColumns; * Settings related utilities. */ public class LauncherSettings { - /** Columns required on table staht will be subject to backup and restore. */ - static interface ChangeLogColumns extends BaseColumns { + + /** + * Favorites. + */ + public static final class Favorites implements BaseColumns { /** * The time of the last update to this row. *

Type: INTEGER

*/ public static final String MODIFIED = "modified"; - } - static public interface BaseLauncherColumns extends ChangeLogColumns { /** * Descriptive name of the gesture that can be displayed to the user. *

Type: TEXT

@@ -84,34 +85,6 @@ public class LauncherSettings { *

Type: BLOB

*/ public static final String ICON = "icon"; - } - - /** - * Workspace Screens. - * - * Tracks the order of workspace screens. - */ - public static final class WorkspaceScreens implements ChangeLogColumns { - - public static final String TABLE_NAME = "workspaceScreens"; - - /** - * The content:// style URL for this table - */ - public static final Uri CONTENT_URI = Uri.parse("content://" + - LauncherProvider.AUTHORITY + "/" + TABLE_NAME); - - /** - * The rank of this screen -- ie. how it is ordered relative to the other screens. - *

Type: INTEGER

- */ - public static final String SCREEN_RANK = "screenRank"; - } - - /** - * Favorites. - */ - public static final class Favorites implements BaseLauncherColumns { public static final String TABLE_NAME = "favorites"; diff --git a/src/com/android/launcher3/MainProcessInitializer.java b/src/com/android/launcher3/MainProcessInitializer.java index a2538930e1..9692d73b23 100644 --- a/src/com/android/launcher3/MainProcessInitializer.java +++ b/src/com/android/launcher3/MainProcessInitializer.java @@ -20,7 +20,6 @@ import android.content.Context; import com.android.launcher3.config.FeatureFlags; import com.android.launcher3.folder.FolderShape; -import com.android.launcher3.graphics.IconShapeOverride; import com.android.launcher3.logging.FileLog; import com.android.launcher3.util.ResourceBasedOverride; @@ -38,7 +37,6 @@ public class MainProcessInitializer implements ResourceBasedOverride { protected void init(Context context) { FileLog.setDir(context.getApplicationContext().getFilesDir()); FeatureFlags.initialize(context); - IconShapeOverride.apply(context); SessionCommitReceiver.applyDefaultUserPrefs(context); FolderShape.init(); } diff --git a/src/com/android/launcher3/SecondaryDropTarget.java b/src/com/android/launcher3/SecondaryDropTarget.java index 6083415d52..0cf6e44c63 100644 --- a/src/com/android/launcher3/SecondaryDropTarget.java +++ b/src/com/android/launcher3/SecondaryDropTarget.java @@ -150,7 +150,7 @@ public class SecondaryDropTarget extends ButtonDropTarget implements OnAlarmList Intent intent = null; UserHandle user = null; if (item != null && - item.itemType == LauncherSettings.BaseLauncherColumns.ITEM_TYPE_APPLICATION) { + item.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPLICATION) { intent = item.getIntent(); user = item.user; } diff --git a/src/com/android/launcher3/ShortcutInfo.java b/src/com/android/launcher3/ShortcutInfo.java index 19c647fd74..c2c3287458 100644 --- a/src/com/android/launcher3/ShortcutInfo.java +++ b/src/com/android/launcher3/ShortcutInfo.java @@ -89,7 +89,7 @@ public class ShortcutInfo extends ItemInfoWithIcon { private int mInstallProgress; public ShortcutInfo() { - itemType = LauncherSettings.BaseLauncherColumns.ITEM_TYPE_SHORTCUT; + itemType = LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT; } public ShortcutInfo(ShortcutInfo info) { @@ -114,24 +114,23 @@ public class ShortcutInfo extends ItemInfoWithIcon { @TargetApi(Build.VERSION_CODES.N) public ShortcutInfo(ShortcutInfoCompat shortcutInfo, Context context) { user = shortcutInfo.getUserHandle(); - itemType = LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT; + itemType = Favorites.ITEM_TYPE_DEEP_SHORTCUT; updateFromDeepShortcutInfo(shortcutInfo, context); } @Override public void onAddToDatabase(ContentWriter writer) { super.onAddToDatabase(writer); - writer.put(LauncherSettings.BaseLauncherColumns.TITLE, title) - .put(LauncherSettings.BaseLauncherColumns.INTENT, getIntent()) - .put(LauncherSettings.Favorites.RESTORED, status); + writer.put(Favorites.TITLE, title) + .put(Favorites.INTENT, getIntent()) + .put(Favorites.RESTORED, status); if (!usingLowResIcon()) { writer.putIcon(iconBitmap, user); } if (iconResource != null) { - writer.put(LauncherSettings.BaseLauncherColumns.ICON_PACKAGE, iconResource.packageName) - .put(LauncherSettings.BaseLauncherColumns.ICON_RESOURCE, - iconResource.resourceName); + writer.put(Favorites.ICON_PACKAGE, iconResource.packageName) + .put(Favorites.ICON_RESOURCE, iconResource.resourceName); } } @@ -189,7 +188,7 @@ public class ShortcutInfo extends ItemInfoWithIcon { @Override public ComponentName getTargetComponent() { ComponentName cn = super.getTargetComponent(); - if (cn == null && (itemType == LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT + if (cn == null && (itemType == Favorites.ITEM_TYPE_SHORTCUT || hasStatusFlag(FLAG_SUPPORTS_WEB_UI))) { // Legacy shortcuts and promise icons with web UI may not have a componentName but just // a packageName. In that case create a dummy componentName instead of adding additional diff --git a/src/com/android/launcher3/Utilities.java b/src/com/android/launcher3/Utilities.java index 65f0703278..a0f005c432 100644 --- a/src/com/android/launcher3/Utilities.java +++ b/src/com/android/launcher3/Utilities.java @@ -31,6 +31,7 @@ import android.content.res.Resources; import android.graphics.Bitmap; import android.graphics.Matrix; import android.graphics.Paint; +import android.graphics.Point; import android.graphics.Rect; import android.graphics.RectF; import android.os.Build; @@ -587,6 +588,21 @@ public final class Utilities { handler.sendMessage(msg); } + /** + * Parses a string encoded using {@link #getPointString(int, int)} + */ + public static Point parsePoint(String point) { + String[] split = point.split(","); + return new Point(Integer.parseInt(split[0]), Integer.parseInt(split[1])); + } + + /** + * Encodes a point to string to that it can be persisted atomically. + */ + public static String getPointString(int x, int y) { + return String.format(Locale.ENGLISH, "%d,%d", x, y); + } + public interface Consumer { void accept(T var1); } diff --git a/src/com/android/launcher3/Workspace.java b/src/com/android/launcher3/Workspace.java index 47ebb17952..eb26961c32 100644 --- a/src/com/android/launcher3/Workspace.java +++ b/src/com/android/launcher3/Workspace.java @@ -619,9 +619,6 @@ public class Workspace extends PagedView // if this is the last screen, convert it to the empty screen mWorkspaceScreens.put(EXTRA_EMPTY_SCREEN_ID, finalScreen); mScreenOrder.add(EXTRA_EMPTY_SCREEN_ID); - - // Update the model if we have changed any screens - LauncherModel.updateWorkspaceScreenOrder(mLauncher, mScreenOrder); } } @@ -728,9 +725,6 @@ public class Workspace extends PagedView mWorkspaceScreens.put(newId, cl); mScreenOrder.add(newId); - // Update the model for the new screen - LauncherModel.updateWorkspaceScreenOrder(mLauncher, mScreenOrder); - return newId; } @@ -828,13 +822,6 @@ public class Workspace extends PagedView } } - if (!removeScreens.isEmpty()) { - // Update the model if we have changed any screens - mLauncher.getModelWriter().enqueueDeleteRunnable( - () -> LauncherModel.updateWorkspaceScreenOrder(mLauncher, mScreenOrder)); - - } - if (pageShift >= 0) { setCurrentPage(currentPage - pageShift); } @@ -3078,9 +3065,9 @@ public class Workspace extends PagedView mapOverItems(MAP_RECURSE, new ItemOperator() { @Override public boolean evaluate(ItemInfo info, View v) { - if (info instanceof ShortcutInfo && v instanceof BubbleTextView - && packageUserKey.updateFromItemInfo(info)) { - if (updatedDots.contains(packageUserKey)) { + if (info instanceof ShortcutInfo && v instanceof BubbleTextView) { + if (!packageUserKey.updateFromItemInfo(info) + || updatedDots.contains(packageUserKey)) { ((BubbleTextView) v).applyDotState(info, true /* animate */); folderIds.add(info.container); } diff --git a/src/com/android/launcher3/graphics/IconShapeOverride.java b/src/com/android/launcher3/graphics/IconShapeOverride.java deleted file mode 100644 index b636c6d478..0000000000 --- a/src/com/android/launcher3/graphics/IconShapeOverride.java +++ /dev/null @@ -1,214 +0,0 @@ -/* - * Copyright (C) 2017 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.launcher3.graphics; - -import static com.android.launcher3.Utilities.getDevicePrefs; - -import android.annotation.TargetApi; -import android.app.AlarmManager; -import android.app.PendingIntent; -import android.app.ProgressDialog; -import android.content.Context; -import android.content.Intent; -import android.content.res.Resources; -import android.os.Build; -import android.os.SystemClock; -import android.provider.Settings; -import android.text.TextUtils; -import android.util.Log; - -import com.android.launcher3.LauncherAppState; -import com.android.launcher3.LauncherModel; -import com.android.launcher3.R; -import com.android.launcher3.Utilities; -import com.android.launcher3.util.LooperExecutor; - -import java.lang.reflect.Field; - -import androidx.annotation.NonNull; -import androidx.preference.ListPreference; -import androidx.preference.Preference; -import androidx.preference.Preference.OnPreferenceChangeListener; - -/** - * Utility class to override shape of {@link android.graphics.drawable.AdaptiveIconDrawable}. - */ -@TargetApi(Build.VERSION_CODES.O) -public class IconShapeOverride { - - private static final String TAG = "IconShapeOverride"; - - public static final String KEY_PREFERENCE = "pref_override_icon_shape"; - - // Time to wait before killing the process this ensures that the progress bar is visible for - // sufficient time so that there is no flicker. - private static final long PROCESS_KILL_DELAY_MS = 1000; - - private static final int RESTART_REQUEST_CODE = 42; // the answer to everything - - public static boolean isSupported(Context context) { - if (!Utilities.ATLEAST_OREO) { - return false; - } - // Only supported when developer settings is enabled - if (Settings.Global.getInt(context.getContentResolver(), - Settings.Global.DEVELOPMENT_SETTINGS_ENABLED, 0) != 1) { - return false; - } - - try { - if (getSystemResField().get(null) != Resources.getSystem()) { - // Our assumption that mSystem is the system resource is not true. - return false; - } - } catch (Exception e) { - // Ignore, not supported - return false; - } - - return getConfigResId() != 0; - } - - public static void apply(Context context) { - if (!Utilities.ATLEAST_OREO) { - return; - } - String path = getAppliedValue(context); - if (TextUtils.isEmpty(path)) { - return; - } - if (!isSupported(context)) { - return; - } - - // magic - try { - Resources override = - new ResourcesOverride(Resources.getSystem(), getConfigResId(), path); - getSystemResField().set(null, override); - } catch (Exception e) { - Log.e(TAG, "Unable to override icon shape", e); - // revert value. - getDevicePrefs(context).edit().remove(KEY_PREFERENCE).apply(); - } - } - - private static Field getSystemResField() throws Exception { - Field staticField = Resources.class.getDeclaredField("mSystem"); - staticField.setAccessible(true); - return staticField; - } - - private static int getConfigResId() { - return Resources.getSystem().getIdentifier("config_icon_mask", "string", "android"); - } - - private static String getAppliedValue(Context context) { - return getDevicePrefs(context).getString(KEY_PREFERENCE, ""); - } - - public static void handlePreferenceUi(ListPreference preference) { - Context context = preference.getContext(); - preference.setValue(getAppliedValue(context)); - preference.setOnPreferenceChangeListener(new PreferenceChangeHandler(context)); - } - - private static class ResourcesOverride extends Resources { - - private final int mOverrideId; - private final String mOverrideValue; - - @SuppressWarnings("deprecation") - public ResourcesOverride(Resources parent, int overrideId, String overrideValue) { - super(parent.getAssets(), parent.getDisplayMetrics(), parent.getConfiguration()); - mOverrideId = overrideId; - mOverrideValue = overrideValue; - } - - @NonNull - @Override - public String getString(int id) throws NotFoundException { - if (id == mOverrideId) { - return mOverrideValue; - } - return super.getString(id); - } - } - - private static class PreferenceChangeHandler implements OnPreferenceChangeListener { - - private final Context mContext; - - private PreferenceChangeHandler(Context context) { - mContext = context; - } - - @Override - public boolean onPreferenceChange(Preference preference, Object o) { - String newValue = (String) o; - if (!getAppliedValue(mContext).equals(newValue)) { - // Value has changed - ProgressDialog.show(mContext, - null /* title */, - mContext.getString(R.string.icon_shape_override_progress), - true /* indeterminate */, - false /* cancelable */); - new LooperExecutor(LauncherModel.getWorkerLooper()).execute( - new OverrideApplyHandler(mContext, newValue)); - } - return false; - } - } - - private static class OverrideApplyHandler implements Runnable { - - private final Context mContext; - private final String mValue; - - private OverrideApplyHandler(Context context, String value) { - mContext = context; - mValue = value; - } - - @Override - public void run() { - // Synchronously write the preference. - getDevicePrefs(mContext).edit().putString(KEY_PREFERENCE, mValue).commit(); - // Clear the icon cache. - LauncherAppState.getInstance(mContext).getIconCache().clear(); - - // Wait for it - try { - Thread.sleep(PROCESS_KILL_DELAY_MS); - } catch (Exception e) { - Log.e(TAG, "Error waiting", e); - } - - // Schedule an alarm before we kill ourself. - Intent homeIntent = new Intent(Intent.ACTION_MAIN) - .addCategory(Intent.CATEGORY_HOME) - .setPackage(mContext.getPackageName()) - .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); - PendingIntent pi = PendingIntent.getActivity(mContext, RESTART_REQUEST_CODE, - homeIntent, PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_ONE_SHOT); - mContext.getSystemService(AlarmManager.class).setExact( - AlarmManager.ELAPSED_REALTIME, SystemClock.elapsedRealtime() + 50, pi); - - // Kill process - android.os.Process.killProcess(android.os.Process.myPid()); - } - } -} diff --git a/src/com/android/launcher3/model/AddWorkspaceItemsTask.java b/src/com/android/launcher3/model/AddWorkspaceItemsTask.java index 756d44e814..5fc555172f 100644 --- a/src/com/android/launcher3/model/AddWorkspaceItemsTask.java +++ b/src/com/android/launcher3/model/AddWorkspaceItemsTask.java @@ -15,11 +15,11 @@ */ package com.android.launcher3.model; -import android.content.Context; import android.content.Intent; import android.os.UserHandle; import android.util.LongSparseArray; import android.util.Pair; + import com.android.launcher3.AllAppsList; import com.android.launcher3.AppInfo; import com.android.launcher3.FolderInfo; @@ -27,7 +27,6 @@ import com.android.launcher3.InvariantDeviceProfile; import com.android.launcher3.ItemInfo; import com.android.launcher3.LauncherAppState; import com.android.launcher3.LauncherAppWidgetInfo; -import com.android.launcher3.LauncherModel; import com.android.launcher3.LauncherModel.CallbackTask; import com.android.launcher3.LauncherModel.Callbacks; import com.android.launcher3.LauncherSettings; @@ -58,16 +57,12 @@ public class AddWorkspaceItemsTask extends BaseModelUpdateTask { if (mItemList.isEmpty()) { return; } - Context context = app.getContext(); final ArrayList addedItemsFinal = new ArrayList<>(); final IntArray addedWorkspaceScreensFinal = new IntArray(); - // Get the list of workspace screens. We need to append to this list and - // can not use sBgWorkspaceScreens because loadWorkspace() may not have been - // called. - IntArray workspaceScreens = LauncherModel.loadWorkspaceScreensDb(context); synchronized(dataModel) { + IntArray workspaceScreens = dataModel.workspaceScreens.clone(); List filteredItems = new ArrayList<>(); for (Pair entry : mItemList) { @@ -116,9 +111,6 @@ public class AddWorkspaceItemsTask extends BaseModelUpdateTask { } } - // Update the workspace screens - updateScreens(context, workspaceScreens); - if (!addedItemsFinal.isEmpty()) { scheduleCallbackTask(new CallbackTask() { @Override @@ -143,10 +135,6 @@ public class AddWorkspaceItemsTask extends BaseModelUpdateTask { } } - protected void updateScreens(Context context, IntArray workspaceScreens) { - LauncherModel.updateWorkspaceScreenOrder(context, workspaceScreens); - } - /** * Returns true if the shortcuts already exists on the workspace. This must be called after * the workspace has been loaded. We identify a shortcut by its intent. diff --git a/src/com/android/launcher3/model/DbDowngradeHelper.java b/src/com/android/launcher3/model/DbDowngradeHelper.java index cd86b728b1..e5c44d1aad 100644 --- a/src/com/android/launcher3/model/DbDowngradeHelper.java +++ b/src/com/android/launcher3/model/DbDowngradeHelper.java @@ -21,6 +21,7 @@ import android.database.sqlite.SQLiteException; import android.util.Log; import android.util.SparseArray; +import com.android.launcher3.R; import com.android.launcher3.provider.LauncherDbUtils.SQLiteTransaction; import com.android.launcher3.util.IOUtils; @@ -87,8 +88,7 @@ public class DbDowngradeHelper { return helper; } - public static void updateSchemaFile(File schemaFile, int expectedVersion, - Context context, int schemaResId) { + public static void updateSchemaFile(File schemaFile, int expectedVersion, Context context) { try { if (DbDowngradeHelper.parse(schemaFile).version >= expectedVersion) { return; @@ -99,7 +99,7 @@ public class DbDowngradeHelper { // Write the updated schema try (FileOutputStream fos = new FileOutputStream(schemaFile); - InputStream in = context.getResources().openRawResource(schemaResId)) { + InputStream in = context.getResources().openRawResource(R.raw.downgrade_schema)) { IOUtils.copy(in, fos); } catch (IOException e) { Log.e(TAG, "Error writing schema file", e); diff --git a/src/com/android/launcher3/model/GridSizeMigrationTask.java b/src/com/android/launcher3/model/GridSizeMigrationTask.java index 2c1aa745f0..a9ddccb670 100644 --- a/src/com/android/launcher3/model/GridSizeMigrationTask.java +++ b/src/com/android/launcher3/model/GridSizeMigrationTask.java @@ -1,5 +1,8 @@ package com.android.launcher3.model; +import static com.android.launcher3.Utilities.getPointString; +import static com.android.launcher3.Utilities.parsePoint; + import android.content.ComponentName; import android.content.ContentProviderOperation; import android.content.ContentValues; @@ -10,14 +13,12 @@ import android.content.pm.PackageInfo; import android.content.pm.PackageManager; import android.database.Cursor; import android.graphics.Point; -import android.net.Uri; import android.util.Log; import com.android.launcher3.InvariantDeviceProfile; import com.android.launcher3.ItemInfo; import com.android.launcher3.LauncherAppState; import com.android.launcher3.LauncherAppWidgetProviderInfo; -import com.android.launcher3.LauncherModel; import com.android.launcher3.LauncherProvider; import com.android.launcher3.LauncherSettings; import com.android.launcher3.LauncherSettings.Favorites; @@ -28,12 +29,14 @@ import com.android.launcher3.compat.PackageInstallerCompat; import com.android.launcher3.config.FeatureFlags; import com.android.launcher3.util.GridOccupancy; import com.android.launcher3.util.IntArray; +import com.android.launcher3.util.IntSet; import com.android.launcher3.util.IntSparseArrayMap; import java.util.ArrayList; import java.util.Collections; import java.util.HashSet; -import java.util.Locale; + +import androidx.annotation.VisibleForTesting; /** * This class takes care of shrinking the workspace (by maximum of one row and one column), as a @@ -177,11 +180,25 @@ public class GridSizeMigrationTask { return applyOperations(); } + @VisibleForTesting + static IntArray getWorkspaceScreenIds(Context context) { + IntSet set = new IntSet(); + try (Cursor c = context.getContentResolver().query(Favorites.CONTENT_URI, + new String[] {Favorites.SCREEN}, + Favorites.CONTAINER + " = " + Favorites.CONTAINER_DESKTOP, + null, Favorites.SCREEN)) { + while (c.moveToNext()) { + set.add(c.getInt(0)); + } + } + return set.getArray(); + } + /** * @return true if any DB change was made */ protected boolean migrateWorkspace() throws Exception { - IntArray allScreens = LauncherModel.loadWorkspaceScreensDb(mContext); + IntArray allScreens = getWorkspaceScreenIds(mContext); if (allScreens.isEmpty()) { throw new Exception("Unable to get workspace screens"); } @@ -213,9 +230,8 @@ public class GridSizeMigrationTask { int newScreenId = LauncherSettings.Settings.call( mContext.getContentResolver(), LauncherSettings.Settings.METHOD_NEW_SCREEN_ID) - .getInt(LauncherSettings.Settings.EXTRA_VALUE); + .getInt(LauncherSettings.Settings.EXTRA_VALUE); - allScreens.add(newScreenId); for (DbEntry item : placement.finalPlacedItems) { if (!mCarryOver.remove(itemMap.get(item.id))) { throw new Exception("Unable to find matching items"); @@ -228,19 +244,6 @@ public class GridSizeMigrationTask { } } while (!mCarryOver.isEmpty()); - - // Update screens - final Uri uri = LauncherSettings.WorkspaceScreens.CONTENT_URI; - mUpdateOperations.add(ContentProviderOperation.newDelete(uri).build()); - int count = allScreens.size(); - for (int i = 0; i < count; i++) { - ContentValues v = new ContentValues(); - int screenId = allScreens.get(i); - v.put(LauncherSettings.WorkspaceScreens._ID, screenId); - v.put(LauncherSettings.WorkspaceScreens.SCREEN_RANK, i); - mUpdateOperations.add(ContentProviderOperation.newInsert(uri).withValues( - v).build()); - } } return applyOperations(); } @@ -877,15 +880,6 @@ public class GridSizeMigrationTask { return dup; } - private static Point parsePoint(String point) { - String[] split = point.split(","); - return new Point(Integer.parseInt(split[0]), Integer.parseInt(split[1])); - } - - private static String getPointString(int x, int y) { - return String.format(Locale.ENGLISH, "%d,%d", x, y); - } - public static void markForMigration( Context context, int gridX, int gridY, int hotseatSize) { Utilities.getPrefs(context).edit() diff --git a/src/com/android/launcher3/model/LoaderCursor.java b/src/com/android/launcher3/model/LoaderCursor.java index 8efebf14b9..d104a8b22f 100644 --- a/src/com/android/launcher3/model/LoaderCursor.java +++ b/src/com/android/launcher3/model/LoaderCursor.java @@ -380,7 +380,7 @@ public class LoaderCursor extends CursorWrapper { * otherwise marks it for deletion. */ public void checkAndAddItem(ItemInfo info, BgDataModel dataModel) { - if (checkItemPlacement(info, dataModel.workspaceScreens)) { + if (checkItemPlacement(info)) { dataModel.addItem(mContext, info, false); } else { markDeleted("Item position overlap"); @@ -390,7 +390,7 @@ public class LoaderCursor extends CursorWrapper { /** * check & update map of what's occupied; used to discard overlapping/invalid items */ - protected boolean checkItemPlacement(ItemInfo item, IntArray workspaceScreens) { + protected boolean checkItemPlacement(ItemInfo item) { int containerIndex = item.screenId; if (item.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) { final GridOccupancy hotseatOccupancy = @@ -420,12 +420,7 @@ public class LoaderCursor extends CursorWrapper { occupied.put(LauncherSettings.Favorites.CONTAINER_HOTSEAT, occupancy); return true; } - } else if (item.container == LauncherSettings.Favorites.CONTAINER_DESKTOP) { - if (!workspaceScreens.contains(item.screenId)) { - // The item has an invalid screen id. - return false; - } - } else { + } else if (item.container != LauncherSettings.Favorites.CONTAINER_DESKTOP) { // Skip further checking if it is not the hotseat or workspace container return true; } diff --git a/src/com/android/launcher3/model/LoaderTask.java b/src/com/android/launcher3/model/LoaderTask.java index f711787cfe..a4fe57086a 100644 --- a/src/com/android/launcher3/model/LoaderTask.java +++ b/src/com/android/launcher3/model/LoaderTask.java @@ -72,6 +72,7 @@ import com.android.launcher3.shortcuts.ShortcutInfoCompat; import com.android.launcher3.shortcuts.ShortcutKey; import com.android.launcher3.util.ComponentKey; import com.android.launcher3.util.IntArray; +import com.android.launcher3.util.IntSet; import com.android.launcher3.util.LooperIdleLock; import com.android.launcher3.util.MultiHashMap; import com.android.launcher3.util.PackageManagerHelper; @@ -293,7 +294,6 @@ public class LoaderTask implements Runnable { final HashMap installingPkgs = mPackageInstaller.updateAndGetActiveSessionCache(); mFirstScreenBroadcast = new FirstScreenBroadcast(installingPkgs); - mBgDataModel.workspaceScreens.addAll(LauncherModel.loadWorkspaceScreensDb(context)); Map shortcutKeyToPinnedShortcuts = new HashMap<>(); final LoaderCursor c = new LoaderCursor(contentResolver.query( @@ -780,21 +780,15 @@ public class LoaderTask implements Runnable { new Handler(LauncherModel.getWorkerLooper())); } - // Remove any empty screens - IntArray unusedScreens = mBgDataModel.workspaceScreens.clone(); + // Initialize the screens array. Using an InstSet ensures that the screen ids + // are sorted. + IntSet screenSet = new IntSet(); for (ItemInfo item: mBgDataModel.itemsIdMap) { - int screenId = item.screenId; - if (item.container == LauncherSettings.Favorites.CONTAINER_DESKTOP && - unusedScreens.contains(screenId)) { - unusedScreens.removeValue(screenId); + if (item.container == LauncherSettings.Favorites.CONTAINER_DESKTOP) { + screenSet.add(item.screenId); } } - - // If there are any empty screens remove them, and update. - if (unusedScreens.size() != 0) { - mBgDataModel.workspaceScreens.removeAllValues(unusedScreens); - LauncherModel.updateWorkspaceScreenOrder(context, mBgDataModel.workspaceScreens); - } + mBgDataModel.workspaceScreens.addAll(screenSet.getArray()); } } diff --git a/src/com/android/launcher3/model/ModelWriter.java b/src/com/android/launcher3/model/ModelWriter.java index f7961d5d87..ca5428d94d 100644 --- a/src/com/android/launcher3/model/ModelWriter.java +++ b/src/com/android/launcher3/model/ModelWriter.java @@ -333,7 +333,7 @@ public class ModelWriter { * {@link #commitDelete()} is called (or abandoned if {@link #abortDelete()} is called). * Otherwise, we run the Runnable immediately. */ - public void enqueueDeleteRunnable(Runnable r) { + private void enqueueDeleteRunnable(Runnable r) { if (mPreparingToUndo) { mDeleteRunnables.add(r); } else { diff --git a/src/com/android/launcher3/provider/ImportDataTask.java b/src/com/android/launcher3/provider/ImportDataTask.java index e1b26980c1..86fcc06802 100644 --- a/src/com/android/launcher3/provider/ImportDataTask.java +++ b/src/com/android/launcher3/provider/ImportDataTask.java @@ -33,7 +33,6 @@ import android.os.Process; import android.text.TextUtils; import android.util.ArrayMap; import android.util.SparseBooleanArray; -import android.util.SparseIntArray; import com.android.launcher3.AutoInstallsLayout.LayoutParserCallback; import com.android.launcher3.DefaultLayoutParser; @@ -43,8 +42,6 @@ import com.android.launcher3.LauncherProvider; import com.android.launcher3.LauncherSettings; import com.android.launcher3.LauncherSettings.Favorites; import com.android.launcher3.LauncherSettings.Settings; -import com.android.launcher3.LauncherSettings.WorkspaceScreens; -import com.android.launcher3.R; import com.android.launcher3.Utilities; import com.android.launcher3.Workspace; import com.android.launcher3.compat.UserManagerCompat; @@ -73,7 +70,6 @@ public class ImportDataTask { private final Context mContext; - private final Uri mOtherScreensUri; private final Uri mOtherFavoritesUri; private int mHotseatSize; @@ -82,41 +78,14 @@ public class ImportDataTask { private ImportDataTask(Context context, String sourceAuthority) { mContext = context; - mOtherScreensUri = Uri.parse("content://" + - sourceAuthority + "/" + WorkspaceScreens.TABLE_NAME); mOtherFavoritesUri = Uri.parse("content://" + sourceAuthority + "/" + Favorites.TABLE_NAME); } public boolean importWorkspace() throws Exception { - IntArray allScreens = LauncherDbUtils.getScreenIdsFromCursor( - mContext.getContentResolver().query(mOtherScreensUri, null, null, null, - LauncherSettings.WorkspaceScreens.SCREEN_RANK)); FileLog.d(TAG, "Importing DB from " + mOtherFavoritesUri); - // During import we reset the screen IDs to 0-indexed values. - if (allScreens.isEmpty()) { - // No thing to migrate - FileLog.e(TAG, "No data found to import"); - return false; - } - mHotseatSize = mMaxGridSizeX = mMaxGridSizeY = 0; - - // Build screen update - ArrayList screenOps = new ArrayList<>(); - int count = allScreens.size(); - SparseIntArray screenIdMap = new SparseIntArray(count); - for (int i = 0; i < count; i++) { - ContentValues v = new ContentValues(); - v.put(LauncherSettings.WorkspaceScreens._ID, i); - v.put(LauncherSettings.WorkspaceScreens.SCREEN_RANK, i); - screenIdMap.put(allScreens.get(i), i); - screenOps.add(ContentProviderOperation.newInsert( - LauncherSettings.WorkspaceScreens.CONTENT_URI).withValues(v).build()); - } - mContext.getContentResolver().applyBatch(LauncherProvider.AUTHORITY, screenOps); - importWorkspaceItems(allScreens.get(0), screenIdMap); - + importWorkspaceItems(); GridSizeMigrationTask.markForMigration(mContext, mMaxGridSizeX, mMaxGridSizeY, mHotseatSize); // Create empty DB flag. @@ -130,17 +99,17 @@ public class ImportDataTask { * 2) For home screen entries, maps the screen id based on {@param screenIdMap} * 3) In the end fills any holes in hotseat with items from default hotseat layout. */ - private void importWorkspaceItems( - int firstScreenId, SparseIntArray screenIdMap) throws Exception { + private void importWorkspaceItems() throws Exception { String profileId = Long.toString(UserManagerCompat.getInstance(mContext) .getSerialNumberForUser(Process.myUserHandle())); boolean createEmptyRowOnFirstScreen; if (FeatureFlags.QSB_ON_FIRST_SCREEN.get()) { try (Cursor c = mContext.getContentResolver().query(mOtherFavoritesUri, null, - // get items on the first row of the first screen - "profileId = ? AND container = -100 AND screen = ? AND cellY = 0", - new String[]{profileId, Integer.toString(firstScreenId)}, + // get items on the first row of the first screen (min screen id) + "profileId = ? AND container = -100 AND cellY = 0 AND screen = " + + "(SELECT MIN(screen) FROM favorites WHERE container = -100)", + new String[]{profileId}, null)) { // First row of first screen is not empty createEmptyRowOnFirstScreen = c.moveToNext(); @@ -164,7 +133,7 @@ public class ImportDataTask { Favorites.PROFILE_ID + " = ?", new String[]{profileId}, // Get the items sorted by container, so that the folders are loaded // before the corresponding items. - Favorites.CONTAINER)) { + Favorites.CONTAINER + " , " + Favorites.SCREEN)) { // various columns we expect to exist. final int idIndex = c.getColumnIndexOrThrow(Favorites._ID); @@ -186,6 +155,7 @@ public class ImportDataTask { SparseBooleanArray mValidFolders = new SparseBooleanArray(); ContentValues values = new ContentValues(); + Integer firstScreenId = null; while (c.moveToNext()) { values.clear(); int id = c.getInt(idIndex); @@ -202,16 +172,21 @@ public class ImportDataTask { switch (container) { case Favorites.CONTAINER_DESKTOP: { - Integer newScreenId = screenIdMap.get(screen); - if (newScreenId == null) { - FileLog.d(TAG, String.format("Skipping item %d, type %d not on a valid screen %d", id, type, screen)); + if (screen < Workspace.FIRST_SCREEN_ID) { + FileLog.d(TAG, String.format( + "Skipping item %d, type %d not on a valid screen %d", + id, type, screen)); continue; } + if (firstScreenId == null) { + firstScreenId = screen; + } // Reset the screen to 0-index value - screen = newScreenId; - if (createEmptyRowOnFirstScreen && screen == Workspace.FIRST_SCREEN_ID) { + if (createEmptyRowOnFirstScreen && firstScreenId.equals(screen)) { // Shift items by 1. cellY++; + // Change the screen id to first screen + screen = Workspace.FIRST_SCREEN_ID; } mMaxGridSizeX = Math.max(mMaxGridSizeX, cellX + spanX); @@ -219,7 +194,7 @@ public class ImportDataTask { break; } case Favorites.CONTAINER_HOTSEAT: { - mHotseatSize = Math.max(mHotseatSize, (int) screen + 1); + mHotseatSize = Math.max(mHotseatSize, screen + 1); break; } default: @@ -378,18 +353,13 @@ public class ImportDataTask { return false; } - private static int getMyHotseatLayoutId(Context context) { - return LauncherAppState.getIDP(context).numHotseatIcons <= 5 - ? R.xml.dw_phone_hotseat - : R.xml.dw_tablet_hotseat; - } - /** * Extension of {@link DefaultLayoutParser} which only allows icons and shortcuts. */ private static class HotseatLayoutParser extends DefaultLayoutParser { public HotseatLayoutParser(Context context, LayoutParserCallback callback) { - super(context, null, callback, context.getResources(), getMyHotseatLayoutId(context)); + super(context, null, callback, context.getResources(), + LauncherAppState.getIDP(context).defaultLayoutId); } @Override @@ -434,6 +404,12 @@ public class ImportDataTask { // No need to add more items. return 0; } + if (!Integer.valueOf(Favorites.CONTAINER_HOTSEAT) + .equals(values.getAsInteger(Favorites.CONTAINER))) { + // Ignore items which are not for hotseat. + return 0; + } + Intent intent; try { intent = Intent.parseUri(values.getAsString(Favorites.INTENT), 0); diff --git a/src/com/android/launcher3/provider/LauncherDbUtils.java b/src/com/android/launcher3/provider/LauncherDbUtils.java index ab0703fed2..b79478ac6f 100644 --- a/src/com/android/launcher3/provider/LauncherDbUtils.java +++ b/src/com/android/launcher3/provider/LauncherDbUtils.java @@ -25,11 +25,9 @@ import android.util.Log; import com.android.launcher3.LauncherAppState; import com.android.launcher3.LauncherSettings.Favorites; -import com.android.launcher3.LauncherSettings.WorkspaceScreens; import com.android.launcher3.util.IntArray; -import java.util.ArrayList; -import java.util.Collection; +import java.util.Locale; /** * A set of utility methods for Launcher DB used for DB updates and migration. @@ -47,26 +45,25 @@ public class LauncherDbUtils { */ public static boolean prepareScreenZeroToHostQsb(Context context, SQLiteDatabase db) { try (SQLiteTransaction t = new SQLiteTransaction(db)) { - // Get the existing screens - IntArray screenIds = getScreenIdsFromCursor(db.query(WorkspaceScreens.TABLE_NAME, - null, null, null, null, null, WorkspaceScreens.SCREEN_RANK)); + // Get the first screen + final int firstScreenId; + try (Cursor c = db.rawQuery(String.format(Locale.ENGLISH, + "SELECT MIN(%1$s) from %2$s where %3$s = %4$d", + Favorites.SCREEN, Favorites.TABLE_NAME, Favorites.CONTAINER, + Favorites.CONTAINER_DESKTOP), null)) { - if (screenIds.isEmpty()) { - // No update needed - t.commit(); - return true; - } - if (screenIds.get(0) != 0) { - // First screen is not 0, we need to rename screens - if (screenIds.contains(0)) { - // There is already a screen 0. First rename it to a different screen. - int newScreenId = 1; - while (screenIds.contains(newScreenId)) newScreenId++; - renameScreen(db, 0, newScreenId); + if (!c.moveToNext()) { + // No update needed + t.commit(); + return true; } + firstScreenId = c.getInt(0); + } + + if (firstScreenId != 0) { // Rename the first screen to 0. - renameScreen(db, screenIds.get(0), 0); + renameScreen(db, firstScreenId, 0); } // Check if the first row is empty @@ -89,31 +86,19 @@ public class LauncherDbUtils { private static void renameScreen(SQLiteDatabase db, int oldScreen, int newScreen) { String[] whereParams = new String[] { Integer.toString(oldScreen) }; - ContentValues values = new ContentValues(); - values.put(WorkspaceScreens._ID, newScreen); - db.update(WorkspaceScreens.TABLE_NAME, values, "_id = ?", whereParams); - - values.clear(); values.put(Favorites.SCREEN, newScreen); db.update(Favorites.TABLE_NAME, values, "container = -100 and screen = ?", whereParams); } - /** - * Parses the cursor containing workspace screens table and returns the list of screen IDs - */ - public static IntArray getScreenIdsFromCursor(Cursor sc) { - try { - return iterateCursor(sc, - sc.getColumnIndexOrThrow(WorkspaceScreens._ID), new IntArray()); - } finally { - sc.close(); - } - } - - public static IntArray iterateCursor(Cursor c, int columnIndex, IntArray out) { - while (c.moveToNext()) { - out.add(c.getInt(columnIndex)); + public static IntArray queryIntArray(SQLiteDatabase db, String tableName, String columnName, + String selection, String groupBy, String orderBy) { + IntArray out = new IntArray(); + try (Cursor c = db.query(tableName, new String[] { columnName }, selection, null, + groupBy, null, orderBy)) { + while (c.moveToNext()) { + out.add(c.getInt(0)); + } } return out; } diff --git a/src/com/android/launcher3/settings/SettingsActivity.java b/src/com/android/launcher3/settings/SettingsActivity.java index 77682e55e3..6e7188f3c9 100644 --- a/src/com/android/launcher3/settings/SettingsActivity.java +++ b/src/com/android/launcher3/settings/SettingsActivity.java @@ -32,11 +32,9 @@ import com.android.launcher3.LauncherFiles; import com.android.launcher3.R; import com.android.launcher3.Utilities; import com.android.launcher3.config.FeatureFlags; -import com.android.launcher3.graphics.IconShapeOverride; import com.android.launcher3.uioverrides.plugins.PluginManagerWrapper; import com.android.launcher3.util.SecureSettingsObserver; -import androidx.preference.ListPreference; import androidx.preference.Preference; import androidx.preference.PreferenceFragment; import androidx.preference.PreferenceFragment.OnPreferenceStartFragmentCallback; @@ -185,13 +183,6 @@ public class SettingsActivity extends Activity case ADD_ICON_PREFERENCE_KEY: return Utilities.ATLEAST_OREO; - case IconShapeOverride.KEY_PREFERENCE: - if (!IconShapeOverride.isSupported(getActivity())) { - return false; - } - IconShapeOverride.handlePreferenceUi((ListPreference) preference); - return true; - case ALLOW_ROTATION_PREFERENCE_KEY: if (getResources().getBoolean(R.bool.allow_rotation)) { // Launcher supports rotation by default. No need to show this setting. diff --git a/src/com/android/launcher3/util/IntSet.java b/src/com/android/launcher3/util/IntSet.java index 63499b06d6..2459fad327 100644 --- a/src/com/android/launcher3/util/IntSet.java +++ b/src/com/android/launcher3/util/IntSet.java @@ -48,4 +48,15 @@ public class IntSet { public int size() { return mArray.size(); } + + public IntArray getArray() { + return mArray; + } + + public static IntSet wrap(IntArray array) { + IntSet set = new IntSet(); + set.mArray.addAll(array); + Arrays.sort(set.mArray.mValues, 0, set.mArray.mSize); + return set; + } } diff --git a/src/com/android/launcher3/util/RaceConditionTracker.java b/src/com/android/launcher3/util/RaceConditionTracker.java index 8eafb551dc..8b0678784a 100644 --- a/src/com/android/launcher3/util/RaceConditionTracker.java +++ b/src/com/android/launcher3/util/RaceConditionTracker.java @@ -22,6 +22,9 @@ package com.android.launcher3.util; * orders. */ public class RaceConditionTracker { + public final static boolean ENTER = true; + public final static boolean EXIT = false; + public interface EventProcessor { void onEvent(String eventName); } @@ -35,4 +38,22 @@ public class RaceConditionTracker { public static void onEvent(String eventName) { if (sEventProcessor != null) sEventProcessor.onEvent(eventName); } + + public static void onEvent(String eventName, boolean isEnter) { + if (sEventProcessor != null) { + sEventProcessor.onEvent(enterExitEvt(eventName, isEnter)); + } + } + + public static String enterExitEvt(String eventName, boolean isEnter) { + return eventName + ":" + (isEnter ? "enter" : "exit"); + } + + public static String enterEvt(String eventName) { + return enterExitEvt(eventName, ENTER); + } + + public static String exitEvt(String eventName) { + return enterExitEvt(eventName, EXIT); + } } diff --git a/src/com/android/launcher3/util/TraceHelper.java b/src/com/android/launcher3/util/TraceHelper.java index 4aa2f37885..a321bcc205 100644 --- a/src/com/android/launcher3/util/TraceHelper.java +++ b/src/com/android/launcher3/util/TraceHelper.java @@ -39,7 +39,7 @@ public class TraceHelper { private static final boolean SYSTEM_TRACE = false; private static final ArrayMap sUpTimes = ENABLED ? new ArrayMap<>() : null; - public static void beginSection(String sectionName) { + public static synchronized void beginSection(String sectionName) { if (ENABLED) { MutableLong time = sUpTimes.get(sectionName); if (time == null) { @@ -55,7 +55,7 @@ public class TraceHelper { } } - public static void partitionSection(String sectionName, String partition) { + public static synchronized void partitionSection(String sectionName, String partition) { if (ENABLED) { MutableLong time = sUpTimes.get(sectionName); if (time != null && time.value >= 0) { @@ -78,7 +78,7 @@ public class TraceHelper { } } - public static void endSection(String sectionName, String msg) { + public static synchronized void endSection(String sectionName, String msg) { if (ENABLED) { MutableLong time = sUpTimes.get(sectionName); if (time != null && time.value >= 0) { diff --git a/tests/src/com/android/launcher3/model/LoaderCursorTest.java b/tests/src/com/android/launcher3/model/LoaderCursorTest.java index ac1be1777a..df935b8dbe 100644 --- a/tests/src/com/android/launcher3/model/LoaderCursorTest.java +++ b/tests/src/com/android/launcher3/model/LoaderCursorTest.java @@ -18,16 +18,12 @@ import com.android.launcher3.ShortcutInfo; import com.android.launcher3.Utilities; import com.android.launcher3.compat.LauncherAppsCompat; import com.android.launcher3.icons.BitmapInfo; -import com.android.launcher3.util.IntArray; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; -import java.util.ArrayList; -import java.util.Arrays; - -import static com.android.launcher3.LauncherSettings.BaseLauncherColumns.INTENT; +import static com.android.launcher3.LauncherSettings.Favorites.INTENT; import static com.android.launcher3.LauncherSettings.Favorites.CELLX; import static com.android.launcher3.LauncherSettings.Favorites.CELLY; import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER; @@ -148,81 +144,57 @@ public class LoaderCursorTest { assertEquals(ITEM_TYPE_SHORTCUT, info.itemType); } - @Test - public void checkItemPlacement_wrongWorkspaceScreen() { - IntArray workspaceScreens = IntArray.wrap(1, 3); - mIDP.numRows = 4; - mIDP.numColumns = 4; - mIDP.numHotseatIcons = 3; - - // Item on unknown screen are not placed - assertFalse(mLoaderCursor.checkItemPlacement( - newItemInfo(0, 0, 1, 1, CONTAINER_DESKTOP, 4), workspaceScreens)); - assertFalse(mLoaderCursor.checkItemPlacement( - newItemInfo(0, 0, 1, 1, CONTAINER_DESKTOP, 5), workspaceScreens)); - assertFalse(mLoaderCursor.checkItemPlacement( - newItemInfo(0, 0, 1, 1, CONTAINER_DESKTOP, 2), workspaceScreens)); - - assertTrue(mLoaderCursor.checkItemPlacement( - newItemInfo(0, 0, 1, 1, CONTAINER_DESKTOP, 1), workspaceScreens)); - assertTrue(mLoaderCursor.checkItemPlacement( - newItemInfo(0, 0, 1, 1, CONTAINER_DESKTOP, 3), workspaceScreens)); - - } @Test public void checkItemPlacement_outsideBounds() { - IntArray workspaceScreens = IntArray.wrap(1, 2); mIDP.numRows = 4; mIDP.numColumns = 4; mIDP.numHotseatIcons = 3; // Item outside screen bounds are not placed assertFalse(mLoaderCursor.checkItemPlacement( - newItemInfo(4, 4, 1, 1, CONTAINER_DESKTOP, 1), workspaceScreens)); + newItemInfo(4, 4, 1, 1, CONTAINER_DESKTOP, 1))); } @Test public void checkItemPlacement_overlappingItems() { - IntArray workspaceScreens = IntArray.wrap(1, 2); mIDP.numRows = 4; mIDP.numColumns = 4; mIDP.numHotseatIcons = 3; // Overlapping items are not placed assertTrue(mLoaderCursor.checkItemPlacement( - newItemInfo(0, 0, 1, 1, CONTAINER_DESKTOP, 1), workspaceScreens)); + newItemInfo(0, 0, 1, 1, CONTAINER_DESKTOP, 1))); assertFalse(mLoaderCursor.checkItemPlacement( - newItemInfo(0, 0, 1, 1, CONTAINER_DESKTOP, 1), workspaceScreens)); + newItemInfo(0, 0, 1, 1, CONTAINER_DESKTOP, 1))); assertTrue(mLoaderCursor.checkItemPlacement( - newItemInfo(0, 0, 1, 1, CONTAINER_DESKTOP, 2), workspaceScreens)); + newItemInfo(0, 0, 1, 1, CONTAINER_DESKTOP, 2))); assertFalse(mLoaderCursor.checkItemPlacement( - newItemInfo(0, 0, 1, 1, CONTAINER_DESKTOP, 2), workspaceScreens)); + newItemInfo(0, 0, 1, 1, CONTAINER_DESKTOP, 2))); assertTrue(mLoaderCursor.checkItemPlacement( - newItemInfo(1, 1, 1, 1, CONTAINER_DESKTOP, 1), workspaceScreens)); + newItemInfo(1, 1, 1, 1, CONTAINER_DESKTOP, 1))); assertTrue(mLoaderCursor.checkItemPlacement( - newItemInfo(2, 2, 2, 2, CONTAINER_DESKTOP, 1), workspaceScreens)); + newItemInfo(2, 2, 2, 2, CONTAINER_DESKTOP, 1))); assertFalse(mLoaderCursor.checkItemPlacement( - newItemInfo(3, 2, 1, 2, CONTAINER_DESKTOP, 1), workspaceScreens)); + newItemInfo(3, 2, 1, 2, CONTAINER_DESKTOP, 1))); } @Test public void checkItemPlacement_hotseat() { - IntArray workspaceScreens = new IntArray(); mIDP.numRows = 4; mIDP.numColumns = 4; mIDP.numHotseatIcons = 3; // Hotseat items are only placed based on screenId assertTrue(mLoaderCursor.checkItemPlacement( - newItemInfo(3, 3, 1, 1, CONTAINER_HOTSEAT, 1), workspaceScreens)); + newItemInfo(3, 3, 1, 1, CONTAINER_HOTSEAT, 1))); assertTrue(mLoaderCursor.checkItemPlacement( - newItemInfo(3, 3, 1, 1, CONTAINER_HOTSEAT, 2), workspaceScreens)); + newItemInfo(3, 3, 1, 1, CONTAINER_HOTSEAT, 2))); assertFalse(mLoaderCursor.checkItemPlacement( - newItemInfo(3, 3, 1, 1, CONTAINER_HOTSEAT, 3), workspaceScreens)); + newItemInfo(3, 3, 1, 1, CONTAINER_HOTSEAT, 3))); } private ItemInfo newItemInfo(int cellX, int cellY, int spanX, int spanY, diff --git a/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java b/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java index bc5aaeeb75..8c03e4b5f6 100644 --- a/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java +++ b/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java @@ -17,6 +17,7 @@ package com.android.launcher3.ui; import static androidx.test.InstrumentationRegistry.getInstrumentation; +import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; import android.app.Instrumentation; @@ -25,11 +26,13 @@ import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.content.pm.LauncherActivityInfo; +import android.content.pm.PackageManager; import android.os.Process; import android.os.RemoteException; import android.view.Surface; import androidx.test.InstrumentationRegistry; +import androidx.test.uiautomator.By; import androidx.test.uiautomator.BySelector; import androidx.test.uiautomator.Direction; import androidx.test.uiautomator.UiDevice; @@ -77,6 +80,7 @@ public abstract class AbstractLauncherUiTest { public static final long SHORT_UI_TIMEOUT= 300; public static final long DEFAULT_UI_TIMEOUT = 10000; + protected static final int LONG_WAIT_TIME_MS = 60000; protected MainThreadExecutor mMainThreadExecutor = new MainThreadExecutor(); protected final UiDevice mDevice; @@ -325,4 +329,33 @@ public abstract class AbstractLauncherUiTest { return intent == null ? null : (Intent) intent.getParcelableExtra(Intent.EXTRA_INTENT); } } + + protected void startAppFast(String packageName) { + final Instrumentation instrumentation = getInstrumentation(); + final Intent intent = instrumentation.getContext().getPackageManager(). + getLaunchIntentForPackage(packageName); + intent.addCategory(Intent.CATEGORY_LAUNCHER); + intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + instrumentation.getTargetContext().startActivity(intent); + assertTrue(packageName + " didn't start", + mDevice.wait(Until.hasObject(By.pkg(packageName).depth(0)), LONG_WAIT_TIME_MS)); + } + + protected String resolveSystemApp(String category) { + return getInstrumentation().getContext().getPackageManager().resolveActivity( + new Intent(Intent.ACTION_MAIN).addCategory(category), + PackageManager.MATCH_SYSTEM_ONLY). + activityInfo.packageName; + } + + protected void closeLauncherActivity() { + // Destroy Launcher activity. + executeOnLauncher(launcher -> { + if (launcher != null) { + launcher.finish(); + } + }); + waitForLauncherCondition( + "Launcher still active", launcher -> launcher == null, DEFAULT_UI_TIMEOUT); + } } diff --git a/tests/src/com/android/launcher3/ui/widget/BindWidgetTest.java b/tests/src/com/android/launcher3/ui/widget/BindWidgetTest.java index 22bc05c942..8b8e43626c 100644 --- a/tests/src/com/android/launcher3/ui/widget/BindWidgetTest.java +++ b/tests/src/com/android/launcher3/ui/widget/BindWidgetTest.java @@ -297,10 +297,6 @@ public class BindWidgetTest extends AbstractLauncherUiTest { if (screenId > Workspace.FIRST_SCREEN_ID) { screenId = Workspace.FIRST_SCREEN_ID; } - ContentValues v = new ContentValues(); - v.put(LauncherSettings.WorkspaceScreens._ID, screenId); - v.put(LauncherSettings.WorkspaceScreens.SCREEN_RANK, 0); - mResolver.insert(LauncherSettings.WorkspaceScreens.CONTENT_URI, v); // Insert the item ContentWriter writer = new ContentWriter(mTargetContext); diff --git a/tests/src/com/android/launcher3/util/RaceConditionReproducer.java b/tests/src/com/android/launcher3/util/RaceConditionReproducer.java index c4350de0b7..316e40d825 100644 --- a/tests/src/com/android/launcher3/util/RaceConditionReproducer.java +++ b/tests/src/com/android/launcher3/util/RaceConditionReproducer.java @@ -43,6 +43,11 @@ import java.util.concurrent.TimeUnit; * executing events in previously unseen order. It does it by postponing execution of threads that * would lead to an already seen sequence. * + * If an event A occurs before event B in the sequence, this is how execution order looks like: + * Events: ... A ... B ... + * Events and instructions, guaranteed order: + * (instructions executed prior to A) A ... B (instructions executed after B) + * * Each iteration has 3 parts (phases). * Phase 1. Picking a previously seen event subsequence that we believe can have previously unseen * continuations. Reproducing this sequence by pausing threads that would lead to other sequences. @@ -178,6 +183,10 @@ public class RaceConditionReproducer implements RaceConditionTracker.EventProces mReproString = reproString; } + public RaceConditionReproducer(String... reproSequence) { + this(String.join("|", reproSequence)); + } + public synchronized String getCurrentSequenceString() { return mCurrentSequence.toString(); } diff --git a/tests/tapl/com/android/launcher3/tapl/Workspace.java b/tests/tapl/com/android/launcher3/tapl/Workspace.java index c63822e2ea..c45f0f0d81 100644 --- a/tests/tapl/com/android/launcher3/tapl/Workspace.java +++ b/tests/tapl/com/android/launcher3/tapl/Workspace.java @@ -46,28 +46,16 @@ public final class Workspace extends Home { @NonNull public AllApps switchToAllApps() { verifyActiveContainer(); - if (mLauncher.isSwipeUpEnabled()) { - int midX = mLauncher.getDevice().getDisplayWidth() / 2; - int height = mLauncher.getDevice().getDisplayHeight(); - // Swipe from 6/7ths down the screen to 1/7th down the screen. - mLauncher.swipe( - midX, - height * 6 / 7, - midX, - height / 7 - ); - } else { - // Swipe from the hotseat to near the top, e.g. 10% of the screen. - final UiObject2 hotseat = mHotseat; - final Point start = hotseat.getVisibleCenter(); - final int endY = (int) (mLauncher.getDevice().getDisplayHeight() * 0.1f); - mLauncher.swipe( - start.x, - start.y, - start.x, - endY - ); - } + // Swipe from the hotseat to near the top, e.g. 10% of the screen. + final UiObject2 hotseat = mHotseat; + final Point start = hotseat.getVisibleCenter(); + final int endY = (int) (mLauncher.getDevice().getDisplayHeight() * 0.1f); + mLauncher.swipe( + start.x, + start.y, + start.x, + endY + ); return new AllApps(mLauncher); }